什么是electron 简单来说,electron是使用html,css,js,nodejs,Native Api构建的跨平台的软件开发框架
简介 | Electron (electronjs.org)
要点:
应用广泛的跨平台的桌面应用开发框架
electron的本质是结合了Chromium与Nodejs
使用HTML,CSS,JS等Web技术构建桌面应用程序
Chromium可以简单理解为一个简洁版的浏览器
electron的技术架构 electron可以简单理解为如下组合
具体架构如下图所示:
主进程是整个程序的入口,渲染进程由主进程进行管理。
主进程和渲染进程原则上是相互隔离的,只能通过IPC的方式进行通信。
Native API是一套通用的操作系统API,这是实现跨平台重要的原因。
主进程使用的是Nodejs环境,渲染进程使用的是Chromium(Web环境),需要注意区分。
程序运行在主进程上,每当打开一个页面就会产生一个渲染进程,渲染进程需要由主进程管理。在主进程中,可以使用Nodejs的所有语法,在渲染进程中,可以使用JS语法,进程与进程之间相互隔离。进程与进程之间可以使用IPC的方式进行通信,主进程与各渲染进程只需直接通过IPC的方式,对于渲染进程和渲染进程之间,需要通过主进程间接实现通信。
项目搭建 快速上手 确保安装了Nodejs
初始化Node项目
在package.json
中,author
和description
对于打包来说是必填项
1 2 3 4 5 6 7 8 9 10 11 12 { "name" : "demo" , "version" : "1.0.0" , "main" : "index.js" , "scripts" : { "test" : "echo \"Error: no test specified\" && exit 1" } , "keywords" : [ ] , "author" : "gcnanmu" , "license" : "ISC" , "description" : "a electron demo app" }
安装electron
1 2 3 npm install --save-dev electron 或者 npm install electron -D
package.json
,添加main
和start
字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { "name" : "demo" , "version" : "1.0.0" , "main" : "main.js" , "scripts" : { "start" : "electron ." , "test" : "echo \"Error: no test specified\" && exit 1" } , "keywords" : [ ] , "author" : "gcnanmu" , "license" : "ISC" , "description" : "a electron demo app" , "devDependencies" : { "electron" : "^32.0.1" } }
main
字段指明了主进程所在js的位置
start
设置了运行程序的命令,也可以直接使用electron .
命令启动程序
在根目录创建main.js
,创建app并渲染一个窗口。
1 2 3 4 5 6 7 8 9 10 const { app, BrowserWindow } = require ("electron" );app.on ("ready" ,()=> { new BrowserWindow ({ width : 800 , height : 600 , }) })
app
表示程序对象
BrowserWindow
表示渲染窗口,可传入多个配置项
在终端运行npm start
,未出现报错且跳出一个窗口则运行成功。
从URL加载页面 修改main.js
,为渲染窗口指定b站的URL进行加载
1 2 3 4 5 6 7 8 9 10 11 const { app, BrowserWindow } = require ("electron" );app.on ("ready" ,()=> { const win = new BrowserWindow ({ width : 800 , height : 600 , autoHideMenuBar : true , }) win.loadURL ("https://www.bilibili.com" ) })
打开开发者模式后,在终端可以看到如下的警告信息,这个警告不会影响程序的正常运行,不用理会。
1 2 3 [28648:0827*/*201847.314:ERROR:CONSOLE(1)] "Request Autofill.enable failed. {"code":-32601,"message":"'Autofill.enable' wasn't found"}", source: devtools://devtools/bundled/core/protocol_client/protocol_client.js (1*)* [28648:0827*/*201847.314:ERROR:CONSOLE(1)] "Request Autofill.setAddresses failed. {"code":-32601,"message":"'Autofill.setAddresses' wasn't found"}", source: devtools://devtools/bundled/core/protocol_client/protocol_client.js (1)
github相关的issue: 议题 #41614 · electron/electron (github.com)
加载本地页面 在根目录创建一个名为render
文件夹来存放渲染的页面
1 2 3 4 5 6 7 8 9 . ├─ node_modules ├─ package-lock.json ├─ package.json ├─ main.js └─ render └─ index ├─ index.html └─ index.js
index.html
写入我们想要展示的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Electron_Demo</title > </head > <body > <h1 > electron学习</h1 > <button id ="btn1" > 点我</button > <script src ="./index.js" > </script > </body > </html >
index.js
中实现相应的操作
1 2 3 4 5 const btn1 = document .getElementById ("btn1" );btn1.onclick = () => { alert ("Hello World" ); };
在main.js
中加载index.html
1 2 3 4 5 6 7 8 9 10 11 12 const { app, BrowserWindow } = require ("electron" );app.on ("ready" , () => { const win = new BrowserWindow ({ width : 800 , height : 600 , autoHideMenuBar : true , }); win.loadFile ("./render/index/index.html" ); });
在启动的electron应用中,可以使用Ctrl + Shift + I
打开开发者工具,在Console
中会出现如下的安全警告信息
VM4 sandbox_bundle:2 Electron Security Warning (Insecure Content-Security-Policy) This renderer process has either no Content Security Policy set or a policy with “unsafe-eval” enabled. This exposes users of this app to unnecessary security risks.
VM4 sandbox_bundle:2 Electron 安全警告(不安全的内容安全策略)该渲染器进程要么没有设置内容安全策略,要么启用了 “不安全-评估 “策略。 策略或启用了 “不安全-评估 “策略。这使此应用程序的用户面临 此应用程序的用户面临不必要的安全风险。
For more information and help, consulthttps://electronjs.org/docs/tutorial/security . This warning will not show up once the app is packaged.
想要传达的意思是该渲染器进程没有设置内容安全策略 ,Insecure Content-Security-Policy
的相关信息可以在内容安全策略(CSP) - HTTP | MDN (mozilla.org) 中查到。
按照MDN网站的提示,需要我们在渲染页面index.html
中的meta
标签中加入如下的信息
1 <meta http-equiv ="Content-Security-Policy" content ="default-src 'self'; img-src https://*; child-src 'none';" />
1 2 3 4 5 6 7 8 9 10 11 12 13 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <meta http-equiv ="Content-Security-Policy" content ="default-src 'self'; img-src https://*; child-src 'none';" /> <title > Electron_Demo</title > </head > <body > <h1 > electron学习</h1 > <script src ="./index.js" > </script > </body > </html >
添加后警告消失。
完善窗口行为 对于Windows系统,如果当前应用的所有窗口被关闭,那么视为程序运行结束,进程被关闭。但对于MacOS系统来说,只要Docker栏中的应用图标存在,程序就不会被关闭,而是进入一个特殊的休眠状态,再次点击docker栏中的图标,则会激活程序,创建窗口。
基于上述系统之间的进程管理策略的差异性,在electron中使用如下代码来适配相关的策略
1 2 3 4 5 6 7 8 9 app.on ('activate' , () => { if (BrowserWindow .getAllWindows ().length === 0 ) createWindow () }) app.on ('window-all-closed' , () => { if (process.platform !== 'darwin' ) app.quit () })
代码解释:
activate
- 当程序处于被激活状态时,如果当前渲染窗口的个数为0 则自动创建窗口
window-all-closed
- 当所有窗口被关闭且当前平台为windows
时,程序结束执行
修改后的main.js
代码如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const { app, BrowserWindow } = require ("electron" );function createWindow ( ) { const win = new BrowserWindow ({ width : 800 , height : 600 , autoHideMenuBar : true , }); win.loadFile ("./render/index/index.html" ); } app.on ("ready" , () => { createWindow (); app.on ("activate" , () => { if (BrowserWindow .getAllWindows ().length === 0 ) createWindow (); }); }); app.on ("window-all-closed" , () => { if (process.platform !== "darwin" ) app.quit (); });
运行得到的结果如下
自动重启 如果要使主进程修改后自动执行,可使用nodemon
安装
修改package.json
中的start
命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { "name" : "demo" , "version" : "1.0.0" , "main" : "main.js" , "scripts" : { "start" : "nodemon --exec electron ." , "test" : "echo \"Error: no test specified\" && exit 1" } , "keywords" : [ ] , "author" : "gcnanmu" , "license" : "ISC" , "description" : "a electron demo app" , "devDependencies" : { "electron" : "^32.0.1" } }
对于渲染进程,每次修改后可使用Ctrl + R
刷新页面内容,如果要让nodemon
将渲染页面也纳入检测范围,需要在根目录创建nodemon.json
文件来设置相关配置。
1 2 3 4 5 6 7 8 9 { "ignore" : [ "node modules" , "dist" ] , "restartable" : "r" , "watch" : [ "*.*" ] , "ext" : "html,js,css" }
配置解释
ignore
- 要忽略的文件
restartable
- 重启的命令 在命令行输入r
即可重启程序
watch
- 监测的文件范围,这里表示处理ignore
的所有文件
ext
- 要监测的文件后缀名
Preload脚本 在前文的技术架构中提到,主进程与渲染进程是相互隔离的,且主进程为Nodejs环境,而渲染进程为Web环境。因此无法在渲染进程中使用__dirname
,不能在主进程中使用window
对象。这样的好处是保证了进程的运行安全,但对于复杂一些的操作就很难实现。
为了实现IPC通信,主进程与渲染进程之间需要一个桥梁,这个桥梁便是预加载脚本。
预加载脚本很特殊,它能够访问部分Nodejs的api,本质上属于削弱后的Nodejs环境。
现在有一个这样的需求,我需要把Nodejs版本、electron版本显示到当前的渲染页面index.html
上。
相应的版本信息只有Nodejs环境中才可以获取,预加载脚本属于Nodejs环境,可以通过预加载脚本获取版本信息,但是展示信息要在渲染进程完成。因此需要主进程将版本信息传递给渲染进程。那么必然涉及到IPC通信
在根目录创建preload.js
1 2 3 4 5 6 7 8 const {contextBridge, ipcRenderer} = require ('electron' )contextBridge.exposeInMainWorld ('api' , { version : { Electron : process.versions .electron , Chrome : process.versions .chrome , Node : process.versions .node } })
contextBridge
直译为上下文桥梁,exposeInMainWorld
的作用是将其中的信息暴露给渲染进程。暴露后,api对象会出现在window
对象中
在main.js
中需指明预加载脚本的位置(绝对路径),这样之后就建立起了IPC通信。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const { app, BrowserWindow } = require ("electron" );const path = require ("path" );function createWindow ( ) { const win = new BrowserWindow ({ width : 800 , height : 600 , autoHideMenuBar : true , webPreferences : { preload : path.resolve (__dirname, "./preload.js" ), }, }); win.loadFile ("./render/index/index.html" ); }
在index.html
中创建一个新的按钮
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <meta http-equiv ="Content-Security-Policy" content ="default-src 'self'; img-src https://*; child-src 'none';" /> <title > Electron_Demo</title > </head > <body > <h1 > electron学习</h1 > <button id ="btn1" > 点我</button > <br > <br > <hr > <button id ="btn2" > 点我获取版本信息</button > <script src ="./index.js" > </script > </body > </html >
为新按钮绑定事件,同时index.js
中使用api
对象
1 2 3 4 5 6 7 8 9 10 const btn1 = document .getElementById ("btn1" );const btn2 = document .getElementById ("btn2" );btn1.onclick = () => { alert ("Hello World" ); }; btn2.onclick = () => { console .log (api.version ); }
通过preload.js
预加载脚本,就能够在实现在渲染进程中展示版本信息
需要注意加载的顺序:主进程 -> 预加载脚本 -> 渲染进程
进程通信(IPC) 通过一个案例来更好展示IPC之间的通信原理。
在根目录新建一个data.txt
,当写入按钮点击时,将input
标签中的内容写入text.txt
。当点击读取按钮时,会将data.txt
中的内容显示在下面的span
标签中。初始页面构成如下图所示
index.html
如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <meta http-equiv ="Content-Security-Policy" content ="default-src 'self'; img-src https://*; child-src 'none';" /> <title > Demo</title > </head > <body > <h1 > IPC通信</h1 > <input type ="text" > <br > <br > <button id ="btn1" > 写入</button > <hr > <button id ="btn2" > 读取</button > <br > <br > <span id ="showData" > 当前未读取</span > <script src ="./index.js" > </script > </body > </html >
渲染进程 → 主进程(单向) 为了实现写入按钮的功能,需要先获取input标签中的内容,将内容传递给预加载脚本preload.js
,preload.js
再将数据暴露给主进程,主进程获取到数据后,再调用Nodejs的fs模块将内容写入data.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const { contextBridge, ipcRenderer } = require ("electron" );contextBridge.exposeInMainWorld ("api" , { version : { Electron : process.versions .electron , Chrome : process.versions .chrome , Node : process.versions .node , }, writeFile : (data ) => { ipcRenderer.send ("write-file" , data); }, });
1 2 3 4 5 6 7 8 9 const btn1 = document .getElementById ("btn1" );btn1.onclick = () => { const data = document .querySelector ("input" ).value ; api.writeFile (data); };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const { app, BrowserWindow , ipcMain } = require ("electron" );const path = require ("path" );const fs = require ("fs" );function writeFile (data ) { fs.writeFileSync ("./data.txt" , data); } app.on ("ready" , () => { createWindow (); app.on ("activate" , () => { if (BrowserWindow .getAllWindows ().length === 0 ) createWindow (); }); ipcMain.on ("write-file" , (event, data ) => { writeFile (data); }); });
整个执行的流程如图所示
渲染进程 ↔ 主进程(双向) 接着完善读取按钮的实现逻辑,我们不仅要读取文件的内容,还需要读出的内容发送给渲染进程。因此属于双向的IPC通信,需要按钮发出命令,主进程读取了文件的具体内容后,将内容返回给渲染进程。使用的不再是send
和on
,而是invoke
和handle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const { contextBridge, ipcRenderer } = require ("electron" );contextBridge.exposeInMainWorld ("myApi" , { version : { Electron : process.versions .electron , Chrome : process.versions .chrome , Node : process.versions .node , }, writeFile : (data ) => { ipcRenderer.send ("write-file" , data); }, readFile : () => { return ipcRenderer.invoke ("read-file" ); }, });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const btn1 = document .getElementById ("btn1" );const btn2 = document .getElementById ("btn2" );btn1.onclick = () => { const data = document .querySelector ("input" ).value ; myApi.writeFile (data); }; btn2.onclick = async () => { const span = document .getElementById ("showData" ); const data = await myApi.readFile (); span.innerHTML = data; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const { app, BrowserWindow , ipcMain } = require ("electron" );const path = require ("path" );const fs = require ("fs" );app.on ("ready" , () => { createWindow (); app.on ("activate" , () => { if (BrowserWindow .getAllWindows ().length === 0 ) createWindow (); }); ipcMain.on ("write-file" , (event, data ) => { writeFile (data); }); ipcMain.handle ("read-file" , (event ) => { const data = fs.readFileSync ("./data.txt" , "utf-8" ).toString (); return data; }); });
得到的效果如下,读取得到的内容和data.txt
中的内容一致。
主进程 → 渲染进程(单向) 这次从主进程给渲染进程发消息。我们实现加载窗体过4s后发送一个message显示到渲染进程中。需要在主进程中使用send
,预加载脚本中使用on
。但是ipcMain
中无send
方法,需要使用win.webContents
来实现,其余并无二致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 function createWindow ( ) { const win = new BrowserWindow ({ width : 800 , height : 600 , icon : "./assert/Comm Discord Dark.ico" , autoHideMenuBar : true , webPreferences : { preload : path.resolve (__dirname, "./preload.js" ), }, }); win.loadFile ("./render/index/index.html" ); setTimeout (() => { win.webContents .send ("message" , "Hello from main process" ); }, 4000 ); } app.on ("ready" , () => { createWindow (); })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const { contextBridge, ipcRenderer } = require ("electron" );console .log ("preload.js" );contextBridge.exposeInMainWorld ("api" , { version : { Electron : process.versions .electron , Chrome : process.versions .chrome , Node : process.versions .node , }, writeFile : (data ) => { ipcRenderer.send ("write-file" , data); }, readFile : () => { return ipcRenderer.invoke ("read-file" ); }, getMessage : (callback ) => { return ipcRenderer.on ("message" , callback); }, });
1 2 3 4 5 6 7 8 window .onload = () => { api.getMessage (logMessage) } function logMessage (event, data ) { alert (data); }
实现效果如下
事实上,使用渲染进程和主进程双向通信的方法来替换主进程向渲染进程的单向通信也是可行的。
打包应用 使用electron-builder
进行打包
安装
1 npm install electron-builder -D
在package.json
文件中设置相应的打包配置项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 { "name" : "demo" , "version" : "1.0.0" , "main" : "main.js" , "scripts" : { "start" : "nodemon --exec electron ." , "build" : "electron-builder" } , "build" : { "appId" : "demo.electron" , "win" : { "icon" : "./Comm Discord Dark.ico" , "target" : [ { "target" : "nsis" , "arch" : [ "x64" ] } ] } , "nsis" : { "oneClick" : false , "perMachine" : true , "allowToChangeInstallationDirectory" : true } } , "devDependencies" : { "electron" : "^30.0.0" , "electron-builder" : "^24.13.3" } , "author" : "gcnanmu" , "license" : "ISC" , "description" : "a electron demo app" }
相关配置项说明:
name - 应用程序的名称
version - 应用程序的版本
appId - 应用程序的唯一标识符
icon - 应用图标(只影响打包程序的图标)
targe - NSIS 指定使用NSIS作为安装程序的格式
arch - 指定为64位架构
nsis
oneClick - 设置false为使安装程序显示安装向导,而不是一键安装
perMachine - 设置为true表示只为当前用户安装
allowToChangeInstallationDirectory - 设置为true表示在安装的过程中允许选择安装目录
author - 作者信息 会显示在程序右键的详细信息标签栏中
终端运行npm run build
即开始打包,打包过程需要保证网络通畅(能访问github)
1 2 3 4 5 6 7 8 9 > demo@1.0.0 build > electron-builder • electron-builder version=24.13.3 os=10.0.19045 • loaded configuration file=package.json ("build" field) • writing effective config file=dist\builder-effective-config.yaml • packaging platform=win32 arch=x64 electron=32.0.1 appOutDir=dist\win-unpacked • building target=nsis file=dist\demo Setup 1.0.0.exe archs=x64 oneClick=false perMachine=true • building block map blockMapFile=dist\demo Setup 1.0.0.exe.blockmap
打包后得到dist
文件夹
1 2 3 4 5 6 dist ├─ builder-debug.yml ├─ builder-effective-config.yaml ├─ demo Setup 1.0.0.exe ├─ demo Setup 1.0.0.exe.blockmap └─ win-unpacked
其中demo Setup 1.0.0.exe
即为安装程序,在win-unpacked
中还存在一个非安装包形式的可执行程序。
electron-vite 创建脚手架 在了解了electron的基本原理后,按照原理分析,只要最终能够转换为html,css,js文件,那么理论上就可以使用electron。因此前端框架也可以使用electron进行打包。为了方便开发,有开发者将electron
和vite
进行了结合,得到了更加利于electron
开发的vite
脚手架。
alex8088/electron-vite: Next generation Electron build tooling based on Vite 新一代 Electron 开发构建工具,支持源代码保护 (github.com)
electron-vite | 下一代 Electron 开发构建工具
创建一个electron-vite项目
1 npm create @quick-start/electron@latest
跟随指引选择相应的选项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 D:\Web\electron>npm create @quick-start/electron@latest > npx > create-electron √ Project name: ... electron-app √ Select a framework: » vue √ Add TypeScript? ... No / Yes √ Add Electron updater plugin? ... No / Yes √ Enable Electron download mirror proxy? ... No / Yes Scaffolding project in D:\Web\electron\electron-app... Done. Now run: cd electron-app npm install npm run dev D:\Web\electron>cd electron-app D:\Web\electron\electron-app>npm install npm warn deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. npm warn deprecated @humanwhocodes/config-array@0.11.14: Use @eslint/config-array instead npm warn deprecated rimraf@3.0.2: Rimraf versions prior to v4 are no longer supported npm warn deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported npm warn deprecated @humanwhocodes/object-schema@2.0.3: Use @eslint/object-schema instead > electron-app@1.0.0 postinstall > electron-builder install-app-deps • electron-builder version=24.13.3 • loaded configuration file=D:\Web\electron\electron-app\electron-builder.yml added 473 packages in 31s 80 packages are looking for funding run `npm fund` for details
这样就创建成功了。根目录的结构如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 electron-app ├─ .editorconfig ├─ .eslintignore ├─ .eslintrc.cjs ├─ .gitignore ├─ .npmrc ├─ .prettierignore ├─ .prettierrc.yaml ├─ .vscode ├─ README.md ├─ build ├─ electron-builder.yml ├─ electron.vite.config.mjs ├─ node_modules ├─ package-lock.json ├─ package.json ├─ resources └─ src
原理探究 日常开发来说,我们只需关注根目录下src
文件夹的内容
1 2 3 4 5 6 7 8 9 10 11 12 src ├─ main │ └─ index.js ├─ preload │ └─ index.js └─ renderer ├─ index.html └─ src ├─ App.vue ├─ assets ├─ components └─ main.js
从文件夹的命名就能看出端倪,这和前文中提到的electron的基本结构完全一致。
main
- 对应主进程文件
preload
- 对应预加载脚本文件
renderer
- 对应模板文件
观察render
下的src
目录,可以发现为常规的以vue为框架的vite
模板结构。即把原本渲染进程的html文件换为使用vue构建,这样可以无缝衔接原本的vite-vue开发习惯,而不需要其他的额外配置。
在终端输入npm run dev
运行项目,出现下图窗口则运行成功。
点击send IPC
按钮,可以看到控制台输出pong
,看看是否和上文electron的实现逻辑相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <script setup > import Versions from './components/Versions.vue' const ipcHandle = ( ) => window .electron .ipcRenderer .send ('ping' )</script > <template > <img alt ="logo" class ="logo" src ="./assets/electron.svg" /> <div class ="creator" > Powered by electron-vite</div > <div class ="text" > Build an Electron app with <span class ="vue" > Vue</span > </div > <p class ="tip" > Please try pressing <code > F12</code > to open the devTool</p > <div class ="actions" > <div class ="action" > <a href ="https://electron-vite.org/" target ="_blank" rel ="noreferrer" > Documentation</a > </div > <div class ="action" > <a target ="_blank" rel ="noreferrer" @click ="ipcHandle" > Send IPC</a > </div > </div > <Versions /> </template >
从App.vue
中可以看到,Send IPC
绑定了事件ipcHandle
,ipcHandle
直接调用了window.electron.ipcRenderer.send('ping')
,按原则说,渲染进程是不能直接使用window.electron
的,这说明在预加载脚本中有导出electron
。定位到./src/perload/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { contextBridge } from 'electron' import { electronAPI } from '@electron-toolkit/preload' const api = {}if (process.contextIsolated ) { try { contextBridge.exposeInMainWorld ('electron' , electronAPI) contextBridge.exposeInMainWorld ('api' , api) } catch (error) { console .error (error) } } else { window .electron = electronAPI window .api = api }
可以看到,预加载脚本通过contextBridge.exposeInMainWorld
导出了electronAPI
,访问@electron-toolkit/preload
可以发现,其实该文件也只是使用了IpcRendererEvent
,这在代码的第一行就已经体现了。
1 import { IpcRendererEvent } from 'electron' ;
到这就已经解释了为什么渲染进程能过访问window.electron
,那么在主进程中是否有相应的on
处理逻辑呢?答案是肯定的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 app.whenReady ().then (() => { electronApp.setAppUserModelId ('com.electron' ) app.on ('browser-window-created' , (_, window ) => { optimizer.watchWindowShortcuts (window ) }) ipcMain.on ('ping' , () => console .log ('pong' )) createWindow () app.on ('activate' , function ( ) { if (BrowserWindow .getAllWindows ().length === 0 ) createWindow () }) })
在上述代码中可以看到 ipcMain.on('ping', () => console.log('pong'))
。到此就已经证实了,即便使用脚手架,整个过程还是符合electron的基本原理的。
在页面中看到的版本号来自组件Versions.vue
,对应的版本信息也是来自window.electron
1 2 3 4 5 6 7 8 9 10 11 12 13 <script setup > import { reactive } from 'vue' const versions = reactive ({ ...window .electron .process .versions })</script > <template > <ul class ="versions" > <li class ="electron-version" > Electron v{{ versions.electron }}</li > <li class ="chrome-version" > Chromium v{{ versions.chrome }}</li > <li class ="node-version" > Node v{{ versions.node }}</li > </ul > </template >
了解了基本的开发需要用到的文件,可以继续探究package.json
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 { "name" : "electron-app" , "version" : "1.0.0" , "description" : "An Electron application with Vue" , "main" : "./out/main/index.js" , "author" : "example.com" , "homepage" : "https://electron-vite.org" , "scripts" : { "format" : "prettier --write ." , "lint" : "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix" , "start" : "electron-vite preview" , "dev" : "electron-vite dev" , "build" : "electron-vite build" , "postinstall" : "electron-builder install-app-deps" , "build:unpack" : "npm run build && electron-builder --dir" , "build:win" : "npm run build && electron-builder --win" , "build:mac" : "npm run build && electron-builder --mac" , "build:linux" : "npm run build && electron-builder --linux" } , "dependencies" : { "@electron-toolkit/preload" : "^3.0.1" , "@electron-toolkit/utils" : "^3.0.0" } , "devDependencies" : { "@electron-toolkit/eslint-config" : "^1.0.2" , "@rushstack/eslint-patch" : "^1.10.3" , "@vitejs/plugin-vue" : "^5.0.5" , "@vue/eslint-config-prettier" : "^9.0.0" , "electron" : "^31.0.2" , "electron-builder" : "^24.13.3" , "electron-vite" : "^2.3.0" , "eslint" : "^8.57.0" , "eslint-plugin-vue" : "^9.26.0" , "prettier" : "^3.3.2" , "vite" : "^5.3.1" , "vue" : "^3.4.30" } }
可以看到,为了打包不出错,author
和description
都赋予了一个默认值。而且为了方便打包,打包的build
命令也准备齐全。
打包 上文提到脚手架提供了一系列的默认打包命令
build
- 默认打包,会生成一个非安装包的可执行程序和安装程序
build:unpack
- 只会生成一个非安装包的可执行程序
build:win
,build:mac
,build:linux
- 直接指定安装包所属的操作系统,生成对应平台的安装程序
打包的相关设置在根目录的electron-builder.yml
文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 appId: com.electron.app productName: electron-app directories: buildResources: build files: - '!**/.vscode/*' - '!src/*' - '!electron.vite.config.{js,ts,mjs,cjs}' - '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}' - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}' asarUnpack: - resources/** win: executableName: electron-app nsis: artifactName: ${name}-${version}-setup.${ext} shortcutName: ${productName} uninstallDisplayName: ${productName} createDesktopShortcut: always mac: entitlementsInherit: build/entitlements.mac.plist extendInfo: - NSCameraUsageDescription: Application requests access to the device's camera. - NSMicrophoneUsageDescription: Application requests access to the device's microphone. - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. notarize: false dmg: artifactName: ${name}-${version}.${ext} linux: target: - AppImage - snap - deb maintainer: electronjs.org category: Utility appImage: artifactName: ${name}-${version}.${ext} npmRebuild: false publish: provider: generic url: https://example.com/auto-updates electronDownload: mirror: https://npmmirror.com/mirrors/electron/
和上文提到的直接package.json
中配置的选项大差不差,按需设置即可。根据需要运行npm run build
或者其他的命令进行打包。