Electron 继承了 Chromium 的多进程架构,主要包括主进程和渲染进程。
每个 Electron 应用都有一个单一的主进程作为应用程序的入口点。主进程在 Node.js 环境中运行,这意味着它具有 require 模块和使用所有 Node.js API 的能力。
主进程可以通过 Electron 的 app 模块来控制应用程序的生命周期,此外,应用程序的菜单、对话框、托盘图标等与操作系统相关的操作都由主进程控制。
主进程使用 BrowserWindow 模块创建和管理应用程序窗口,每个窗口都在一个单独的渲染进程中运行。
运行于渲染进程中的代码必须遵照 Web 标准,这也意味着渲染进程无权直接访问 Node.js API,但可以通过预加载脚本解决这一问题。
创建一个简单的 Electron 应用
新建一个名为 my-electron-app 的文件夹,在该文件夹下执行:
然后,安装 electron:
接下来,要运行 Electron 项目,先修改 package.json 中的内容:
1 2 3 4 5 6
| { "main": "main.js", "scripts": { "start": "electron ." } }
|
main
字段指定了 Electron 程序的入口文件(主进程入口点),这在 Electron 程序中是必须的,稍后会创建 main.js 文件。
执行以下命令来运行程序:
在根目录下新建 index.html 文件,添加如下内容:
1 2 3 4 5 6 7 8 9 10 11
| <!DOCTYPE html> <html lang="utf-8"> <head> <meta charset="UTF-8"> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"> <title>你好!</title> </head> <body> <h1>你好!</h1> </body> </html>
|
在根目录下新建 main.js 文件,添加如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const { app, BrowserWindow } = require("electron");
const createWindow = () => { const win = new BrowserWindow({ width: 800, height: 600 }) win.loadFile('index.html') }
app.whenReady().then(() => { createWindow() })
|
main.js 文件就是该应用的主进程入口文件,我们在主进程中创建了一个窗口,并将 index.html 加载到窗体中,当我们开启应用时,渲染进程就会将 index.html 文件的内容渲染到窗体中。
由于渲染进程运行在 Web 环境中,因此我们可以在 index.html 的 <body>
中添加一个 <script>
标签来引入脚本,以扩展程序的功能:
1
| <script src="./renderer.js"></script>
|
renderer.js 其实就相当于 Vue 或 React 程序的入口文件。index.html 就相当于 Vue 或 React 项目打包后的 index.html。
这样看来,Electron 的渲染进程的代码完全可以通过 Vue 或 React 框架来开发,那么下面就介绍一下如何将 Electron 和 Vue 结合起来进行开发。
Electron + Vue3 + TS + Vite
首先创建一个 Vue 项目:
安装 Electron:
在根目录下创建 electron 文件夹,在文件夹下创建 main.ts 文件和 preload.ts 文件:
1 2 3 4 5 6 7 8
| import { contextBridge, ipcRenderer } from "electron";
contextBridge.exposeInMainWorld("myApi", { node: () => process.versions.node, inputFile: () => ipcRenderer.invoke("input-file"), outputFile: () => ipcRenderer.invoke("output-file") });
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { app, BrowserWindow, ipcMain, dialog } from "electron"; import { resolve } from "path";
const createWindow = () => { const win = new BrowserWindow({ width: 1920, height: 1080, webPreferences: { preload: resolve(__dirname, "./preload.js") } }); win.loadURL("http://localhost:5173"); }
app.whenReady().then(() => { createWindow(); });
|
- 这里导入预加载脚本用的是
./preload.js
而不是 preload.ts
,这是 vite-plugin-electron 插件的作用,我们稍后介绍。
- 在开发环境中,通过
loadURL
加载 Vue 服务比 loadFile
更加方便。
由于 electron 文件夹在项目根目录下,ts 配置文件没有包含,所以需要修改 ts 配置文件,因为 main.ts 和 preload.ts 文件都运行在 node 环境,因此修改 tsconfig.node.json 文件:
1 2 3 4 5
| { "include": [ "electron/**/*.ts" ] }
|
接下来,安装 vite-plugin-electron 插件:
1
| npm i vite-plugin-electron -D
|
在 vite.config.ts 中配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import electronVite from "vite-plugin-electron/simple";
export default defineConfig({ plugins: [ electronVite({ main: { entry: "electron/main.ts" }, preload: { input: "electron/preload.ts" } }) ] })
|
该插件有以下作用:
- 运行项目时可以同时运行 vue 程序和桌面程序;
- 支持桌面程序热重载;
- 可以通过配置将主进程文件和预加载文件进行 ts 编译和 babel 转义,得到可直接使用的 js 文件。
启动项目时,该插件会在根目录下生成 dist-electron 文件夹,其中是转义后的 main.js 和 preload.js 文件。
这也解释了上面引入预加载脚本时为什么用 preload.js
而不是 preload.ts
。
同样,Electron 的入口文件也应该是 dist-electron/main.js,修改 package.json:
1 2 3
| { "main": "dist-electron/main.js" }
|
启动项目时就像启动普通的 Vue 项目即可:
这时,Electron 程序和 Vue 程序都启动了。
如果想要在桌面程序中像在浏览器中一样进行调试,可以使用快捷键打开控制台:
打包
在打包之前,需要注意一下几个地方:
(1)路由须使用 Hash 模式。
1 2 3 4 5 6 7 8
| import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({ history: createWebHashHistory("/"), routes: [...] })
export default router
|
(2)vite 的基本路径使用相对路径。
1 2 3
| export default defineConfig({ base: "./" })
|
(3)修改主进程入口文件,将窗口载入的资源指向 dist/index.html。
1 2 3 4 5
| if (app.isPackaged) { win.loadFile(path.join(__dirname, "../dist/index.html")); } else { win.loadURL("http://localhost:5173"); }
|
安装 Electron 打包工具 electron-builder:
1
| npm i electron-builder -D
|
在 package.json 中添加打包指令:
1 2 3 4 5
| { "scripts": { "build": "vite build && electron-builder" } }
|
继续在 package.json 中添加打包配置:
1 2 3 4 5 6 7 8 9 10 11 12 13
| { "build": { "productName":"解密工具", "appId": "com.xxx.xxxxx", "copyright":"xxxx", "directories": { "output": "build" }, "win": { "icon": "public/favicon.ico" } } }
|
注意:这里的 icon
图标至少是 256 ✖ 256,不然会报错中断打包,可以不写使用默认图标。
执行 npm run build
后,项目中输出了 build 文件夹,里面有直接可用的 exe 程序,但如果想要生成安装程序,需进行以下配置:
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
| { "build": { "productName": "解密工具", "appId": "com.xxx.xxxxx", "copyright": "xxxx", "directories": { "output": "build" }, "win": { "target": [ { "target": "nsis" } ] }, "nsis": { "oneClick": false, "allowElevation": true, "allowToChangeInstallationDirectory": true, "installerIcon": "public/favicon.ico", "uninstallerIcon": "public/favicon.ico", "installerHeaderIcon": "public/favicon.ico", "createDesktopShortcut": true, "createStartMenuShortcut": true, "shortcutName": "解密工具" } } }
|
意思是需要使用 nsis 工具再将输出的文件夹打包成一个可安装的 exe 程序。
electron-builder 官网。
nsis。
如果打包过程报错:
(1)ERROR: Cannot create symbolic link : 无法创建符号链接,这需要我们以管理员身份去运行打包命令。
(2)node_modules\app-builder-bin\win\x64\app-builder.exe process failed ERR_ELECTRON_BUILDER_CANNOT_EXECUTE,这是网络问题导致的,再次尝试即可。
打包成功后,build 文件夹下会出现一个 Setup.exe 的程序,这就是安装程序。
下面附上 electron-builder 的较完整的配置项:
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| { "build": { "productName":"xxxx", "appId": "com.leon.xxxxx", "copyright":"xxxx", "directories": { "output": "build" }, "nsis": { "oneClick": false, "allowElevation": true, "allowToChangeInstallationDirectory": true, "installerIcon": "./build/icons/aaa.ico", "uninstallerIcon": "./build/icons/bbb.ico", "installerHeaderIcon": "./build/icons/aaa.ico", "createDesktopShortcut": true, "createStartMenuShortcut": true, "shortcutName": "xxxx", "include": "build/script/installer.nsh", }, "publish": [ { "provider": "generic", "url": "http://xxxxx/" } ], "files": [ "dist/electron/**/*" ], "dmg": { "contents": [ { "x": 410, "y": 150, "type": "link", "path": "/Applications" }, { "x": 130, "y": 150, "type": "file" } ] }, "mac": { "icon": "build/icons/icon.icns" }, "win": { "icon": "build/icons/aims.ico", "target": [ { "target": "nsis", "arch": [ "ia32" ] } ] }, "linux": { "icon": "build/icons" } } }
|