Electron 打包与进阶实践

一、项目结构规范

一个生产级别的 Electron 项目目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
my-app/
├── package.json
├── main.js # 主进程入口
├── preload.js # 预加载脚本
├── src/ # 渲染进程源码
│ ├── index.html
│ ├── renderer.js
│ └── styles.css
├── build/ # 构建资源
│ ├── icon.icns # macOS 图标
│ ├── icon.ico # Windows 图标
│ └── icon.png # Linux 图标
├── electron-builder.yml # 打包配置
└── dev-app-update.yml # 开发环境自动更新配置

二、打包(electron-builder)

安装

1
npm install electron-builder --save-dev

配置

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
# electron-builder.yml
appId: com.example.myapp
productName: MyApp
copyright: Copyright © 2024

directories:
output: release
buildResources: build

files:
- main.js
- preload.js
- src/**/*
- node_modules/**/*

mac:
category: public.app-category.utilities
icon: build/icon.icns
target:
- dmg
- zip

win:
icon: build/icon.ico
target:
- target: nsis
arch:
- x64

nsis:
oneClick: false
allowToChangeInstallationDirectory: true
createDesktopShortcut: true

linux:
icon: build/icon.png
target:
- AppImage
- deb

脚本配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// package.json
{
"scripts": {
"pack": "electron-builder --dir",
"dist": "electron-builder",
"dist:win": "electron-builder --win",
"dist:mac": "electron-builder --mac",
"dist:linux": "electron-builder --linux"
},
"build": {
"appId": "com.example.myapp",
"extends": null,
"files": ["main.js", "preload.js", "src/**/*"]
}
}

.gitignore

1
2
3
4
node_modules/
dist/
release/
*.log

打包命令

1
2
3
4
5
6
7
8
9
10
# 打包当前平台
npm run dist

# 打包指定平台(需要对应系统的构建工具)
npm run dist:win # Windows
npm run dist:mac # macOS
npm run dist:linux # Linux

# 打包但不生成安装包(用于测试)
npm run pack

三、自动更新

主进程配置

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
// main.js
const { autoUpdater } = require('electron-updater');

function setupAutoUpdater() {
// 检测更新(启动后自动检查)
autoUpdater.checkForUpdates();

autoUpdater.on('update-available', (info) => {
mainWindow.webContents.send('update-available', info);
});

autoUpdater.on('download-progress', (progress) => {
mainWindow.webContents.send('update-progress', progress);
});

autoUpdater.on('update-downloaded', () => {
// 安装并重启
autoUpdater.quitAndInstall();
});

autoUpdater.on('error', (err) => {
console.error('Update error:', err);
});
}

// 让用户选择何时安装
ipcMain.handle('install-update', () => {
autoUpdater.quitAndInstall();
});

electron-builder 配置

1
2
3
4
5
# electron-builder.yml
publish:
provider: generic
url: https://releases.example.com/
channel: latest
1
2
3
# dev-app-update.yml(开发环境)
provider: generic
url: http://localhost:8080

渲染进程

1
2
3
4
5
6
7
8
9
// preload.js
contextBridge.exposeInMainWorld('api', {
onUpdateAvailable: (callback) =>
ipcRenderer.on('update-available', (_, info) => callback(info)),
onUpdateProgress: (callback) =>
ipcRenderer.on('update-progress', (_, p) => callback(p)),
installUpdate: () =>
ipcRenderer.invoke('install-update'),
});

四、安全最佳实践

1. 启用沙箱和上下文隔离

1
2
3
4
5
6
7
8
const win = new BrowserWindow({
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
sandbox: true,
preload: path.join(__dirname, 'preload.js'),
},
});

2. 限制预加载脚本暴露的 API

1
2
3
4
5
6
7
8
9
10
// ✅ 安全:明确列出暴露的方法
contextBridge.exposeInMainWorld('api', {
readFile: (path) => ipcRenderer.invoke('read-file', path),
writeFile: (path, data) => ipcRenderer.invoke('write-file', path, data),
});

// ❌ 危险:将 ipcRenderer 整个暴露出去
contextBridge.exposeInMainWorld('electron', {
ipcRenderer: ipcRenderer,
});

3. 验证 IPC 参数

1
2
3
4
5
6
7
8
9
// ✅ 验证传入路径是否在允许范围内
const allowedPaths = [app.getPath('documents'), app.getPath('downloads')];

ipcMain.handle('read-file', async (event, filePath) => {
if (!allowedPaths.some(p => filePath.startsWith(p))) {
throw new Error('不允许访问该路径');
}
return fs.readFileSync(filePath, 'utf-8');
});

4. 禁止加载远程内容

1
2
3
win.loadFile('index.html');              // ✅ 加载本地文件
win.loadURL('file://index.html'); // ✅ 本地文件协议
win.loadURL('https://external.com'); // ❌ 加载远程页面(除非必要)

5. 禁用未使用的功能

1
2
3
4
5
6
7
8
win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': ["default-src 'self'"],
},
});
});

五、开发效率

开发模式与热重载

1
npm install electron-reload --save-dev
1
2
3
4
5
6
// main.js(开发环境)
if (process.env.NODE_ENV === 'development') {
require('electron-reload')(__dirname, {
electron: path.join(__dirname, 'node_modules', '.bin', 'electron'),
});
}

主进程与渲染进程分离开发

1
2
3
4
5
6
7
8
// package.json
{
"scripts": {
"dev:renderer": "vite", // 渲染进程使用 Vite 开发
"dev:main": "electron .",
"dev": "concurrently \"npm run dev:renderer\" \"npm run dev:main\""
}
}

Electron 集成 Vite

使用 electron-vite 可以统一管理主进程和渲染进程的构建:

1
npm create @quick-start/electron my-app -- --template vue
1
2
3
4
5
6
my-app/
├── electron/
│ ├── main/ # 主进程
│ └── preload/ # 预加载脚本
├── src/ # 渲染进程(Vue/React)
└── electron.vite.config.ts

六、性能优化

1. 窗口懒加载

1
2
3
4
5
6
7
// 先创建隐藏窗口,内容准备好后再显示
const win = new BrowserWindow({ show: false });
win.loadFile('index.html');

win.once('ready-to-show', () => {
win.show();
});

2. 减少渲染进程内存占用

1
2
3
4
5
6
7
8
9
// 不需要时移除事件监听
win.on('closed', () => {
win = null; // 释放引用,让 GC 回收
});

// 隐藏非活动窗口
mainWin.on('blur', () => {
// 如果窗口隐藏而不是关闭,释放部分资源
});

3. 使用 GPU 加速

1
2
// 启用硬件加速(默认开启)
app.disableHardwareAcceleration(); // 如果不需要可禁用

4. 分析性能

1
2
3
// 使用 Chrome DevTools Performance 面板
// 或编码方式测量
console.log(`内存使用:${process.getSystemMemoryInfo().free / 1024 / 1024} MB`);

七、常见问题

Q1: 打包后的应用打不开

1
2
3
4
# 查看日志
# Windows:%USERPROFILE%\AppData\Roaming\<app-name>\logs\
# macOS:~/Library/Logs/<app-name>/
# Linux:~/.config/<app-name>/logs/

Q2: macOS 下应用被提示损坏

1
2
3
4
# 移除 quarantine 属性
sudo xattr -dr com.apple.quarantine /Applications/MyApp.app

# 或在构建时配置签名

Q3: Windows 下安装包被杀毒软件误报

1
2
3
4
5
6
7
8
9
# electron-builder.yml
nsis:
oneClick: false
perMachine: false # 仅当前用户安装

# 并申请代码签名证书
win:
certificateFile: cert.p12
certificatePassword: password

Q4: 自动更新不生效

1
2
3
4
5
# 检查:
# 1. electron-updater 是否安装
# 2. publish 配置是否正确
# 3. 服务器上的 latest.yml 是否存在
# 4. 版本号是否大于当前版本

Q5: 应用图标不显示

1
2
3
4
5
6
# 需要的图标格式
# Windows:256x256 .ico
# macOS:1024x1024 .icns(或 512x512)
# Linux:256x256 .png

# 在线生成工具:https://icongr.am/ 或 electron-icon-builder

八、推荐学习路径

  1. 掌握主进程和渲染进程的架构(入门与架构篇)
  2. 熟悉 BrowserWindow、Menu、Tray、IPC 等核心 API
  3. 配置 electron-builder 打包
  4. 配置自动更新
  5. 了解安全模型和性能优化
  6. 使用 electron-vite 搭建现代化开发环境