win_webview2 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # Win Webview2
2
+
3
+
4
+ Win Webview2 is a GUI toolkit for building desktop applications with Node.js. It is similar to Electron but significantly smaller because Win Webview2 utilizes Microsoft WebView2, which is already installed on Windows 10 and later versions.
5
+
6
+ ## Building the Application
7
+
8
+ Run the following command:
9
+
10
+ ```sh
11
+ npx ww2_builder
12
+ ```
13
+
14
+ A menu will appear in the terminal with the following options:
15
+ - `init_webview2` – Creates a configuration file in JSON format.
16
+ - `deploy` – Builds the application from your Node.js project.
17
+
18
+ ## Example Configuration File
19
+
20
+ ```json
21
+ {
22
+ "entry_point": "app.js",
23
+ "appname": "openweb",
24
+ "icon_path": "./icon.png",
25
+ "outdir": "./dist"
26
+ }
27
+ ```
package/argtype.ts ADDED
@@ -0,0 +1,14 @@
1
+
2
+
3
+ export type OpenWebArg = {
4
+ url: string
5
+ width: number
6
+ height: number
7
+ kiosk: boolean
8
+ maximize: boolean
9
+ title: string
10
+ }
11
+
12
+ export type OpenDialogFileArg = {
13
+ filter : string
14
+ }
package/index.js ADDED
@@ -0,0 +1,110 @@
1
+
2
+ var ffi = require('ffi-napi');
3
+
4
+ var arc = require('os').arch();
5
+ const path = require("path")
6
+
7
+ let libFilePath = "./bin/Win32/";
8
+ if (arc == "x64") {
9
+ console.log("using x64")
10
+ libFilePath = "./bin/x64/";
11
+ }
12
+
13
+ libFilePath = path.join(__dirname, libFilePath);
14
+
15
+ const mainDllPath = path.join(libFilePath, "win_webview2_lib.dll");
16
+ const webviewDllPath = path.join(libFilePath, "WebView2Loader.dll");
17
+
18
+ ffi.Library(webviewDllPath);
19
+
20
+
21
+
22
+
23
+ module.exports = {
24
+
25
+
26
+ /**
27
+ * @typedef SimpleFFIcallback
28
+ *
29
+ * @param {(string : str)=>void} jscallback
30
+ * @return {SimpleFFIcallback}
31
+ */
32
+ createFFIcallback : (jscallback)=>{
33
+ return ffi.Callback('void', ['string'], (g) => {
34
+ jscallback(g);
35
+ })
36
+ },
37
+
38
+
39
+ // void OpenWebview(
40
+ // const char* url,
41
+ // int width,
42
+ // int height,
43
+ // bool maximize,
44
+ // bool kiosk,
45
+ // const char* title,
46
+ // const char* windowclassname,
47
+ // const char* windowParentclassname,
48
+ // CallbackType cb
49
+
50
+ // )
51
+
52
+ /**
53
+ * @typedef OpenWebviewProp
54
+ * @property {string} url
55
+ * @property {int} width
56
+ * @property {int} height
57
+ * @property {bool} maximize
58
+ * @property {bool} kiosk
59
+ * @property {string} title
60
+ * @property {string} windowclassname
61
+ * @property {string} windowParentclassname
62
+ * @property {SimpleFFIcallback} ffiCallback
63
+ * @property {(string : str)=>void} callback
64
+ *
65
+ * @param {OpenWebviewProp} prop
66
+ */
67
+ openWebview: (prop) => {
68
+ var libm = ffi.Library(mainDllPath, {
69
+ 'OpenWebview': ['void',
70
+ [
71
+ 'string', //url
72
+ 'int', //width
73
+ 'int', // height
74
+ 'bool', // maximize
75
+ 'bool', // kiosk
76
+ 'string', // title.
77
+ 'string', // windowclassname
78
+ 'string', // windowParentclassname
79
+ 'pointer', // CallbackType
80
+ ]]
81
+ });
82
+
83
+ return new Promise((r,x)=>{
84
+ libm.OpenWebview.async(
85
+ prop.url,
86
+ prop.width,
87
+ prop.height,
88
+ prop.maximize,
89
+ prop.kiosk,
90
+ prop.title,
91
+ prop.windowclassname,
92
+ prop.windowParentclassname,
93
+
94
+ prop.ffiCallback,
95
+ (e, res) => {
96
+ if(e){
97
+ x(e);
98
+ } else {
99
+ r(res);
100
+ }
101
+ }
102
+
103
+ )
104
+ })
105
+
106
+
107
+
108
+
109
+ }
110
+ }
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "win_webview2",
3
+ "version": "1.0.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/nnttoo/win_webview2"
7
+ },
8
+ "description": "",
9
+ "main": "index.js",
10
+ "exports": "./winWebview2.mjs",
11
+ "bin": {
12
+ "ww2_builder": "./ww2_builder.mjs"
13
+ },
14
+ "author": "",
15
+ "license": "ISC",
16
+ "dependencies": {
17
+ "prompts": "^2.4.2",
18
+ "rcedit": "^4.0.1",
19
+ "sharp": "^0.33.5",
20
+ "sharp-ico": "^0.1.5"
21
+ }
22
+ }
@@ -0,0 +1,100 @@
1
+ // @ts-check
2
+ let isOncloseSetted = false;
3
+ const prefixWebview = "WINWEBVIEW_";
4
+ const prefixWebviewResult = "WIN_WEBVIEW2_RESULT";
5
+
6
+ /** @type {string | null} */
7
+ let resultDataFromWebview2 = "";
8
+ let resultInUse = false;
9
+
10
+
11
+ /**
12
+ *
13
+ * @param {number} n
14
+ */
15
+ function sleep(n){
16
+ return new Promise((r,x)=>{
17
+ setTimeout(r,n);
18
+ })
19
+ }
20
+
21
+
22
+ /**
23
+ *
24
+ * @param {object } arg
25
+ */
26
+ async function waitingResult(arg){
27
+ if(resultInUse ) return "";
28
+ resultInUse = true;
29
+
30
+ resultDataFromWebview2 = null;
31
+
32
+ var objstr = JSON.stringify(arg);
33
+ console.log(`${prefixWebview}JSON` + objstr);
34
+
35
+ while(resultDataFromWebview2 == null){
36
+ await sleep(200);
37
+ }
38
+
39
+ resultInUse = false;
40
+ return resultDataFromWebview2;
41
+
42
+ }
43
+
44
+
45
+ /**
46
+ *
47
+ * @param {string} data
48
+ */
49
+ function setToResult(data){
50
+ if(!data.startsWith(prefixWebviewResult)) return false;
51
+ let result = data.substring(prefixWebviewResult.length);
52
+ resultDataFromWebview2 = result;
53
+ return true;
54
+ }
55
+
56
+ function setOnClosed() {
57
+ process.stdin.on("data", (dataIn) => {
58
+ let data = dataIn.toString();
59
+ if(setToResult(data)) return;
60
+
61
+ console.log(data);
62
+ if (data == `${prefixWebview}MAINPAGEISCLOSED`) {
63
+ console.log("server diclose");
64
+ process.exit();
65
+ }
66
+ })
67
+ }
68
+
69
+
70
+ /**
71
+ *
72
+ * @param {import("./argtype").OpenWebArg } arg
73
+ */
74
+ export function openWeb(arg) {
75
+ if(!isOncloseSetted){
76
+ isOncloseSetted = true;
77
+ setOnClosed();
78
+ }
79
+
80
+ arg["fun"] = "OpenWeb";
81
+ var objstr = JSON.stringify(arg);
82
+ console.log(`${prefixWebview}JSON` + objstr);
83
+ }
84
+
85
+ /**
86
+ *
87
+ * @param {import("./argtype").OpenDialogFileArg} arg
88
+ * @returns {Promise<string>}
89
+ */
90
+ export function openDialogFile(arg){
91
+ arg["fun"] = "OpenDialogFIle";
92
+ return waitingResult(arg);
93
+ }
94
+
95
+ export async function openDialogFolder(){
96
+ let obj = {
97
+ fun : "OpenDialogFolder"
98
+ };
99
+ return waitingResult(obj);
100
+ }
Binary file
Binary file
Binary file
@@ -0,0 +1 @@
1
+ node ../../../../example/express_dev/index.mjs
Binary file
Binary file
Binary file
@@ -0,0 +1 @@
1
+ node ../../../../example/express_dev/index.mjs
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+
3
+ // @ts-check
4
+
5
+ import prompts from "prompts";
6
+ import fs from 'fs'
7
+ import sharp from "sharp"
8
+ import path from "path";
9
+ import { logPrint } from "./ww2_builder_log.mjs";
10
+ import { WW2Deploy } from "./ww2_builder_deploy.mjs";
11
+
12
+
13
+ /** @typedef {import("./ww2_builder_type").ConfigWW2} ConfigWW2 */
14
+
15
+ /**
16
+ *
17
+ * @param {string} msg
18
+ * @param {string[]} list
19
+ * @returns {Promise<string>}
20
+ */
21
+ async function askWhitList(msg, list) {
22
+ let p = await prompts([
23
+ {
24
+ type: 'select', // Tipe pertanyaan list (menu dropdown)
25
+ name: 'hasilnya',
26
+ message: msg,
27
+ choices: list.map((val) => {
28
+ let r = {
29
+ title: val,
30
+ value: val
31
+ }
32
+ return r
33
+ }), // Opsi
34
+ },
35
+ ]);
36
+ return p.hasilnya;
37
+ }
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ async function run() {
47
+ let p = await askWhitList("menu", [
48
+ "init_webview2",
49
+ "deploy",
50
+ "back",
51
+ "exit"
52
+ ]);
53
+
54
+ if (p == "init_webview2") {
55
+
56
+ await WW2Deploy.initWW2();
57
+
58
+ } else if (p == "deploy") {
59
+ await WW2Deploy.startDeploy();
60
+ }
61
+
62
+
63
+
64
+ if (p == "exit") {
65
+ return;
66
+ }
67
+
68
+ run();
69
+ }
70
+
71
+
72
+ run();
@@ -0,0 +1,171 @@
1
+ // @ts-check
2
+
3
+ /** @typedef {import("./ww2_builder_type").ConfigWW2} ConfigWW2 */
4
+
5
+
6
+ import fs from 'fs'
7
+ import sharp from "sharp"
8
+ import path from "path";
9
+ import { logPrint } from "./ww2_builder_log.mjs";
10
+ import { fileURLToPath } from 'url';
11
+ import rcedit from "rcedit";
12
+ import sharpsToIco from "sharp-ico"
13
+
14
+
15
+ const jsonConfigFilePath = "./win_webview2.json";
16
+
17
+ export class WW2Deploy {
18
+
19
+ /** @type {ConfigWW2 | null} */
20
+
21
+ configObjec = null;
22
+
23
+ /** @type {"x64" | "Win32" } */
24
+ platform = "Win32";
25
+
26
+ /** @type {string | null} */
27
+ outputExeFile = null;
28
+
29
+
30
+
31
+ async makeDistFolder() {
32
+
33
+ if (this.configObjec == null) return;
34
+ let config = this.configObjec;
35
+
36
+ let folderDist = config.outdir;
37
+
38
+ if (!fs.existsSync(folderDist)) {
39
+ await fs.promises.mkdir(folderDist);
40
+ }
41
+
42
+ let libFolder = path.join(folderDist,"lib");
43
+ if (!fs.existsSync(libFolder)) {
44
+ await fs.promises.mkdir(libFolder);
45
+ }
46
+ }
47
+
48
+ async buildIcon() {
49
+ if (this.configObjec == null) return;
50
+
51
+ let config = this.configObjec;
52
+
53
+ let iconPath = config.icon_path;
54
+ if (!fs.existsSync(iconPath)) {
55
+
56
+ logPrint("icon notfound");
57
+ return;
58
+
59
+ }
60
+
61
+ let outputIcon = path.join(config.outdir, "icon.ico");
62
+
63
+ const sizes = [16, 32, 48, 256]; // Ukuran standar untuk rcedit
64
+ const images = await Promise.all(sizes.map(size =>
65
+ sharp(iconPath)
66
+ .resize(size, size)
67
+ .toFormat('png')
68
+ ));
69
+
70
+
71
+ await sharpsToIco.sharpsToIco(images, outputIcon);
72
+
73
+ console.log('✅ Berhasil membuat icon.ico yang valid untuk rcedit!');
74
+
75
+
76
+ }
77
+
78
+ async copyExe() {
79
+ let currentDirMjs = fileURLToPath(import.meta.url);
80
+ let currentDir = path.dirname(currentDirMjs);
81
+
82
+ if (this.configObjec == null) return;
83
+ let config = this.configObjec;
84
+
85
+ logPrint("COpy webview2 exe file");
86
+ logPrint(currentDir);
87
+
88
+ let inputFileExe = path.join(currentDir, "win_lib", this.platform, "CmdWebview2.exe");
89
+
90
+ let outFileExe = path.join(config.outdir, config.appname + ".exe");
91
+ this.outputExeFile = inputFileExe;
92
+ await fs.promises.copyFile(inputFileExe, outFileExe);
93
+
94
+ let inputFileDll = path.join(currentDir, "win_lib", this.platform, "WebView2Loader.dll");
95
+ let outFileDll = path.join(config.outdir, "WebView2Loader.dll");
96
+ await fs.promises.copyFile(inputFileDll, outFileDll);
97
+
98
+ logPrint("copy file success");
99
+ }
100
+
101
+ async editIcon() {
102
+ if (this.outputExeFile == null) return;
103
+ if (this.configObjec == null) return;
104
+
105
+ let object = this.configObjec;
106
+
107
+
108
+ let iconPath = path.join(object.outdir, "icon.ico");
109
+ iconPath = path.resolve(iconPath);
110
+
111
+ await rcedit(this.outputExeFile, {
112
+ icon: iconPath,
113
+ });
114
+ }
115
+
116
+ async readConfig() {
117
+ let str = await fs.promises.readFile(jsonConfigFilePath);
118
+ let jsonObj = JSON.parse(str.toString());
119
+
120
+ this.configObjec = jsonObj;
121
+ }
122
+
123
+ async copyNodeExe(){
124
+ if(this.configObjec == null) return;
125
+ let obj = this.configObjec;
126
+
127
+ let inputNodeExe = process.execPath;
128
+ let outNodeExe = path.join(obj.outdir,"lib","node.exe");
129
+ await fs.promises.copyFile(inputNodeExe, outNodeExe);
130
+ }
131
+
132
+ async createIndexConf(){
133
+ if(this.configObjec == null) return;
134
+ let obj = this.configObjec;
135
+
136
+ let oututFileConf = path.join(obj.outdir,"index.conf");
137
+
138
+ let configCtn = `./lib/node.exe ./${obj.entry_point}`
139
+ await fs.promises.writeFile(oututFileConf,configCtn);
140
+ }
141
+
142
+ static async initWW2() {
143
+ /** @type {ConfigWW2} */
144
+
145
+ let objConfig = {
146
+ entry_point: "app.js",
147
+ appname: "openweb",
148
+ icon_path: "./icon.png",
149
+ outdir: "./dist"
150
+ }
151
+
152
+ let objstr = JSON.stringify(objConfig, null, 2);
153
+ await fs.promises.writeFile(jsonConfigFilePath, objstr);
154
+ }
155
+
156
+
157
+
158
+ static async startDeploy() {
159
+ logPrint("Start Deploy win_webview2");
160
+ let c = new WW2Deploy();
161
+
162
+ await c.readConfig();
163
+ await c.makeDistFolder();
164
+ await c.buildIcon();
165
+ await c.copyExe();
166
+ await c.editIcon();
167
+ await c.copyNodeExe();
168
+ await c.createIndexConf();
169
+
170
+ }
171
+ }
@@ -0,0 +1,3 @@
1
+ export function logPrint(msg) {
2
+ console.log(new Date().toLocaleString() + ": ", msg);
3
+ }
@@ -0,0 +1,7 @@
1
+
2
+ export type ConfigWW2={
3
+ appname : string,
4
+ icon_path : string,
5
+ entry_point : string,
6
+ outdir : string,
7
+ }