Electron

Electron 继承了 Chromium 的多进程架构,主要包括主进程和渲染进程。
每个 Electron 应用都有一个单一的主进程作为应用程序的入口点。主进程在 Node.js 环境中运行,这意味着它具有 require 模块和使用所有 Node.js API 的能力。
主进程可以通过 Electron 的 app 模块来控制应用程序的生命周期,此外,应用程序的菜单、对话框、托盘图标等与操作系统相关的操作都由主进程控制。
主进程使用 BrowserWindow 模块创建和管理应用程序窗口,每个窗口都在一个单独的渲染进程中运行。
运行于渲染进程中的代码必须遵照 Web 标准,这也意味着渲染进程无权直接访问 Node.js API,但可以通过预加载脚本解决这一问题。

创建一个简单的 Electron 应用

新建一个名为 my-electron-app 的文件夹,在该文件夹下执行:

1
npm init

然后,安装 electron:

1
npm i electron -D

接下来,要运行 Electron 项目,先修改 package.json 中的内容:

1
2
3
4
5
6
{
"main": "main.js",
"scripts": {
"start": "electron ."
}
}

main 字段指定了 Electron 程序的入口文件(主进程入口点),这在 Electron 程序中是必须的,稍后会创建 main.js 文件。
执行以下命令来运行程序:

1
npm run start

在根目录下新建 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 项目:

1
npm create vue@latest

安装 Electron:

1
npm i electron -D

在根目录下创建 electron 文件夹,在文件夹下创建 main.ts 文件和 preload.ts 文件:

1
2
3
4
5
6
7
8
// preload.ts
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
// main.ts
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();
});
  1. 这里导入预加载脚本用的是 ./preload.js 而不是 preload.ts,这是 vite-plugin-electron 插件的作用,我们稍后介绍。
  2. 在开发环境中,通过 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"
}
})
]
})

该插件有以下作用:

  1. 运行项目时可以同时运行 vue 程序和桌面程序;
  2. 支持桌面程序热重载;
  3. 可以通过配置将主进程文件和预加载文件进行 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 项目即可:

1
npm run dev

这时,Electron 程序和 Vue 程序都启动了。
如果想要在桌面程序中像在浏览器中一样进行调试,可以使用快捷键打开控制台:

1
Ctrl + shift + I

打包

在打包之前,需要注意一下几个地方:
(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",//项目名 这也是生成的exe文件的前缀名
"appId": "com.leon.xxxxx",//包名
"copyright":"xxxx",//版权 信息
"directories": { // 输出文件夹
"output": "build"
},
"nsis": {
"oneClick": false, // 是否一键安装
"allowElevation": true, // 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。
"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", // 包含的自定义nsis脚本
},
"publish": [
{
"provider": "generic", // 服务器提供商 也可以是GitHub等等
"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"
}
}
}