路线清晰 · 结构成体系 · 强调最佳实践
适用版本:Electron 20+(当前稳定线 Electron 43/44,Chromium 150 · Node 24 · V8 15)
最后校验:2026-08
0. 如何使用这份指南
适用人群
- 有 HTML / CSS / JavaScript 基础,想用 Web 技术做桌面应用的前端/全栈开发者
- 需要把现有 Web 项目打包成跨平台桌面端(macOS / Windows / Linux)
前置知识(建议先掌握)
- JavaScript(ES2020+)、Node.js 基础(CommonJS / ESM、npm、文件路径)
- 任意前端框架其一(React / Vue / Svelte 均可,本指南以 Vite 体系为例)
- 计算机常识:进程、主线程、文件系统、HTTP/HTTPS、数字签名(概念即可)
阅读建议
本指南按"概念 → 机制 → 能力 → 工程化 → 精通"五阶段递进,后一阶段依赖前一阶段的概念。建议按顺序学,把每阶段的"最小可运行示例"亲手敲一遍,而非只看。每阶段末尾有「最佳实践」小结,可直接当 checklist 用。
1. 学习路线图总览(Roadmap)
五个阶段,建议 8 周(每天 1–2 小时)走完。阶段之间有强依赖,箭头表示「必须先理解」:
架构 + Hello World
→
IPC 通信
→
窗口/菜单/存储
→
打包/更新/签名
→
安全/性能/测试
箭头表示「必须先理解」的依赖关系:1→2→3,2、3→4,2、3、4→5。
阶段目标速览
| 阶段 | 核心目标 | 关键产出 | 预计周期 |
|---|---|---|---|
| 一 · 入门基础 | 建立"主进程 / 渲染进程 / 预加载"的心智模型 | 跑通第一个窗口应用 | 1 周 |
| 二 · 核心机制 | 掌握 IPC 与安全通信边界 | 主进程↔渲染进程双向通信示例 | 1.5 周 |
| 三 · 桌面能力 | 用好窗口、菜单、存储、原生 API | 带菜单/托盘/本地存储的小工具 | 2 周 |
| 四 · 工程化 | 脚手架、打包、自动更新、签名 | 可分发安装包 + 自动更新 | 2 周 |
| 五 · 精通 | 安全、性能、测试、调试、架构 | 生产级可维护项目 | 1.5 周 |
2. 阶段一:入门基础
2.1 Electron 是什么 / 不是什么
是什么:用 HTML/CSS/JS 构建跨平台桌面应用的运行时。本质是「Chromium(渲染)+ Node.js(系统能力)」的组合,由同一个 V8 引擎驱动。
不是什么:
- ❌ 不是浏览器——你的代码拥有文件系统、shell、注册表等完整系统权限
- ❌ 不是跨平台"一次编写处处相同"的银弹——各平台有原生差异(菜单、托盘、签名、权限)
- ❌ 不适合处理完全不可信的远程内容(见阶段五安全章)
2.2 核心架构:三个角色
创建 BrowserWindow
系统 API:文件/菜单/托盘
ipcMain 接收请求
受限 Node 子集
React / Vue 等
渲染进程经 preload 的 ipcRenderer 向主进程发起请求;主进程通过 ipcMain 处理并把结果回传。
- 主进程(Main):应用入口(
main.js),一个应用只有一个。负责创建窗口、调用系统 API、管理生命周期。 - 渲染进程(Renderer):每个
BrowserWindow里的网页,就是一个渲染进程。从 Electron 20 起默认运行在沙箱中,不能直接用 Node.js。 - 预加载脚本(Preload):在渲染进程加载前注入,运行在「隔离的 JS context」。它是连接渲染进程(不可信)与主进程(可信)的唯一受控桥。
⚠️ 心智模型一句话:渲染进程 = 受限制的网页;主进程 = 有系统权限的后台服务;preload = 两者之间的安检门。
2.3 环境准备
# Node 18+(建议 20+),npm 9+
node -v
npm init -y
npm install --save-dev electron
# 验证安装
npx electron --version # 应输出版本号(如 v43.x)
2.4 第一个 Hello World
package.json
{
"name": "hello-electron",
"version": "1.0.0",
"main": "main.js",
"scripts": { "start": "electron ." }
}
main.js(主进程)
const { app, BrowserWindow } = require('electron');
const path = require('path');
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
// 以下三项从 Electron 20 起已是默认值,显式写出便于理解
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
},
});
win.loadFile('index.html');
}
app.whenReady().then(createWindow);
index.html(渲染进程)
<!DOCTYPE html>
<html>
<body>
<h1>Hello Electron 👋</h1>
<p>来自主进程的版本:<span id="ver"></span></p>
<script src="renderer.js"></script>
</body>
</html>
preload.js(预加载)
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('api', {
getVersion: () => ipcRenderer.invoke('get-version'),
});
renderer.js
const ver = await window.api.getVersion();
document.getElementById('ver').textContent = ver;
main.js 里补充 IPC 处理
const { ipcMain } = require('electron');
ipcMain.handle('get-version', () => process.versions.electron);
运行:npm start。
2.5 阶段一最佳实践
- ✅ 始终显式声明
webPreferences(contextIsolation / nodeIntegration / sandbox / preload),即使它与默认值相同——可读性与安全意图清晰。 - ✅ 入口文件命名用
main.js,与package.json的main字段一致。 - ❌ 不要在渲染进程里写
require('electron')或nodeIntegration: true。 - ✅ 用
app.whenReady()而非直接调用,避免窗口在 app 未就绪时创建。
3. 阶段二:核心机制(IPC 进程间通信)
3.1 为什么必须 IPC
渲染进程在沙箱里,没有 Node.js、没有系统权限。任何"读文件、写配置、调用系统 API"的需求,都要委托主进程完成。这就是 IPC(Inter-Process Communication)。
3.2 通信原语对照
| 场景 | 渲染→主 | 主→渲染 | 主进程侧 | 渲染侧(经 preload) |
|---|---|---|---|---|
| 请求/响应(双向) | invoke(channel, ...args) | —— | ipcMain.handle(channel, handler) | ipcRenderer.invoke → Promise |
| 单向通知 | send(channel, ...args) | —— | ipcMain.on(channel, listener) | ipcRenderer.send |
| 主推事件 | —— | webContents.send(channel, ...args) | win.webContents.send | ipcRenderer.on(channel, listener) |
3.3 preload + contextBridge(安全桥)
// preload.js —— 只暴露「具体、经过校验」的 API,绝不透传整个 ipcRenderer
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('fs', {
readNote: (name) => ipcRenderer.invoke('note:read', name),
saveNote: (name, content) => ipcRenderer.invoke('note:save', name, content),
onUpdate: (cb) => ipcRenderer.on('note:updated', (_e, d) => cb(d)),
});
渲染进程里就能 await window.fs.readNote('todo'),完全接触不到 ipcRenderer 本身。
3.4 最常用模式:invoke / handle
// 主进程
ipcMain.handle('note:save', async (event, name, content) => {
// 见阶段五:此处必须做类型/路径校验
const safeName = path.basename(name);
const full = path.join(app.getPath('userData'), safeName);
await fs.writeFile(full, content);
return { ok: true };
});
// 渲染进程(经 preload 暴露)
const res = await window.fs.saveNote('todo', '买牛奶');
3.5 阶段二最佳实践
- ✅ preload 只
exposeInMainWorld具体函数,绝不做contextBridge.exposeInMainWorld('electron', { ipcRenderer })(等于把整把钥匙交出去)。 - ✅ 主进程对所有 IPC 入参二次校验(纵深防御),不信渲染端已校验。
- ✅ 用
invoke/handle取代旧的send/on做请求响应,天然返回 Promise,错误处理更干净。 - ✅ 用
event.senderFrame/ 来源校验来确认消息发送方可信(阶段五详述)。
4. 阶段三:桌面能力
4.1 BrowserWindow 深入
const win = new BrowserWindow({
width: 1000, height: 700,
frame: false, // 无边框(自定义标题栏需自己画)
transparent: false,
resizable: true,
webPreferences: { preload, contextIsolation: true, nodeIntegration: false },
});
win.loadURL('https://app.example.com'); // 远程内容务必 HTTPS + 关闭 nodeIntegration
常用能力:win.webContents.openDevTools()、win.on('closed')、win.setAlwaysOnTop(true)、new BrowserWindow({ parent: win, modal: true })(子窗口/弹窗)。
4.2 应用生命周期(app 事件)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit(); // macOS 习惯保留 dock 图标
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow(); // macOS 点击 dock 重建窗口
});
4.3 菜单 / 托盘 / 快捷键
Menu.buildFromTemplate([...])+Menu.setApplicationMenu(menu)(macOS 顶部菜单 / Windows 顶部菜单)- 右键菜单:
win.webContents.on('context-menu', ...)配合menu.popup() Tray:系统托盘常驻(后台应用标配)globalShortcut.register('CommandOrControl+Shift+K', ...):全局快捷键
4.4 系统集成 API
- 对话框:
dialog.showOpenDialog/showSaveDialog/showMessageBox - 通知:
new Notification({ title, body })(注意 Electron 43 起 macOS 支持remove()/removeAll()等精细管理) - 剪贴板:
clipboard.writeText()/readText() - 打开外部:
shell.openExternal(url)(绝不对不可信内容使用,见安全章) - 打开文件:
shell.openPath(path)
4.5 存储与持久化
| 需求 | 推荐方案 |
|---|---|
| 轻量配置 / 用户偏好 | electron-store(基于 JSON,最简单) |
| 结构化数据 / 大量记录 | better-sqlite3(本地 SQLite,原生模块) |
| 密钥 / Token | safeStorage(主进程加密,绝不放渲染进程) |
| 大文件 / 用户文档 | 直接用 fs + dialog 让用户选路径 |
// 主进程用 safeStorage 存敏感信息
const { safeStorage } = require('electron');
const encrypted = safeStorage.encryptString('my-secret-token');
// 解密:safeStorage.decryptString(encrypted)
4.6 协议与文件系统
- 自定义协议:
protocol.registerSchemesAsPrivileged+protocol.handle('app', ...)(比file://更安全,避免 file 协议的安全限制) - 拖拽文件进窗口:
webContents.on('will-navigate')拦截 + 处理electron://深链
4.7 阶段三最佳实践
- ✅ 远程内容(
loadURL)必须 HTTPS +nodeIntegration: false+contextIsolation: true。 - ✅ 敏感凭证只在主进程用
safeStorage,渲染端永远拿不到明文。 - ✅ macOS 遵循「窗口全关也不退出」的交互习惯。
- ✅ 托盘/后台应用要明确退出入口,避免用户"关不掉"。
5. 阶段四:工程化与分发
5.1 脚手架选型(官方推荐 Electron Forge)
npm init electron-app@latest my-app
Electron Forge 是官方维护的全流程工具链:脚手架 + 开发调试 + 打包 + 签名 + 发布一键搞定,新手首选。
5.2 打包工具对比与选型
| 工具 | 定位 | 何时选 |
|---|---|---|
| Electron Forge | 官方全流程(脚手架+打包+发布) | 新项目、想少折腾 |
| electron-builder | 配置极度灵活、格式最全 | 复杂分发、CI/CD、高度定制(dmg/nsis/appx/snap) |
| electron-vite | 与 Vite 深度集成、HMR 极快 | 现代前端框架(React/Vue/Svelte)项目 |
| electron-packager | 仅打包不生成安装器 | 快速原型 / 内部分发 |
选型经验:初期用 Forge 快速起步;进入生产发布、需要精细定制安装包/自动更新服务端时,迁到 electron-builder 很常见。现代前端项目直接
electron-vite体验最好。
5.3 与前端框架集成(Vite + React 示例)
npm create electron-vite@latest
# 选 react-ts 模板
统一在 electron.vite.config.js 管理主进程 / 预加载 / 渲染进程三个入口,开发期热更新,构建期按需编译,包体更小、冷启动更快。
5.4 自动更新(electron-updater)
// 主进程
const { autoUpdater } = require('electron-updater');
app.whenReady().then(() => {
autoUpdater.checkForUpdatesAndNotify();
});
// 在渲染端提示进度、让用户确认重启,避免打断操作
支持 GitHub Releases / S3 / 私有服务器;Windows 用 NSIS/Squirrel,macOS 用 Squirrel.Mac,Electron 41+ 还支持 MSIX 自动更新。
5.5 代码签名与公证
- macOS:必须签名 + 公证(Notarization),否则 Gatekeeper 直接拦截。需 Apple Developer 证书 +
hardened runtime+ 正确的entitlements。 - Windows:Authenticode 代码签名证书,否则 SmartScreen 报警。
- Linux:一般无需签名,但
.deb/.rpm/AppImage要注意依赖。 - CI 里做签名(GitHub Actions / CircleCI),不要把证书私钥提交进仓库。
5.6 多窗口架构
- 主窗口 + 设置窗口:
new BrowserWindow({ parent: mainWin, modal: true })。 - 多独立窗口:用
BrowserWindow.getAllWindows()管理,注意每个窗口都是一个渲染进程,IPC 要带webContents路由。 - 复杂场景用
WebContentsView(新版)替代旧的<webview>,性能和隔离更好。
5.7 阶段四最佳实践
- ✅ 新项目直接用
npm init electron-app@latest,别手写 build 配置起步。 - ✅ 签名/公证放 CI,证书用 secrets 注入,绝不进 git。
- ✅ 自动更新不要静默强制重启,先提示、让用户保存工作。
- ✅ 生产构建开启 asar(默认),Electron 41+ 可启用 ASAR Integrity 增强防篡改。
6. 阶段五:精通(安全 / 性能 / 测试 / 调试)
6.1 安全最佳实践(重中之重)
Electron 不是浏览器,渲染进程一旦被 XSS,攻击者可 require('child_process') 执行任意命令。三条安全支柱从 Electron 20 起默认开启,你的工作是不去关掉它们:
new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true, // ✅ 隔离 preload 与渲染进程
nodeIntegration: false, // ✅ 渲染进程无 Node.js
sandbox: true, // ✅ Chromium 沙箱
webSecurity: true, // ✅ 同源策略
},
});
官方安全检查清单(必做项)
1. 只加载安全内容(远程用 HTTPS,不用 HTTP)
2. 远程内容绝不开启 nodeIntegration
3. 所有渲染进程开启 contextIsolation
4. 开启进程沙箱 sandbox: true
5. 加载远程内容的 session 设置 ses.setPermissionRequestHandler()
6. 不关闭 webSecurity
7. 定义严格 CSP,例如 Content-Security-Policy: script-src 'self'
8. 不开 allowRunningInsecureContent / experimentalFeatures / enableBlinkFeatures
9. <webview> 不用 allowpopups,校验其 options/params
10. 禁用或限制导航(will-navigate 拦截)、限制新建窗口
11. 不对不可信内容用 shell.openExternal
12. 使用最新版 Electron
13. 校验所有 IPC 消息的发送方
14. 用自定义协议替代 file://
15. 检查可关闭的 Fuses(如 runAsNode、nodeCliInspect)
纵深防御:主进程二次校验示例
ipcMain.handle('note:save', async (event, filename, content) => {
if (typeof filename !== 'string' || typeof content !== 'string')
throw new Error('参数类型非法');
const safeName = path.basename(filename); // 防路径穿越
const full = path.join(app.getPath('userData'), safeName);
if (!full.startsWith(app.getPath('userData')))
throw new Error('路径逃逸被拦截');
if (content.length > 10 * 1024 * 1024) throw new Error('文件过大');
await fs.writeFile(full, content);
return { ok: true };
});
6.2 性能优化
- 主进程轻量:重活(解压、大计算)放到 utility process(
utilityProcess.fork),避免阻塞 UI。 - 渲染进程按需加载、代码分割(Vite/Rollup 天然支持)。
- 控制
webPreferences开销:能用sandbox就开,减少 preload 体积。 - 大列表用虚拟滚动;避免渲染进程里跑长同步任务。
- Electron 43 起主进程用 V8 启动快照、preload 编译为字节码缓存,启动更快——保持版本更新即自动受益。
6.3 测试(Spectron 已弃用)
| 工具 | 用途 |
|---|---|
| Playwright | E2E 测试(官方推荐替代 Spectron),可驱动真实窗口 |
| Electron-Vitest | 单元/集成测试,热更新快,现代项目首选 |
@electron/fuses |
校验打包后的安全 fuse 配置 |
npm i -D @playwright/test
# 配置 test 指向编译后的 Electron 入口即可写 .spec.ts
6.4 调试
- 渲染进程:窗口内
openDevTools(),或用 Vite 的浏览器 DevTools。 - 主进程:
- VS Code:
launch.json配"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron","args": ["."] - 或命令行
electron --inspect=5858 .,Chrome 打开chrome://inspect - preload 调试:Electron 43 起 preload 堆栈能显示正确文件路径和行号。
6.5 崩溃监控与稳定性
crashReporter收集主进程/渲染进程崩溃堆栈(上报到自有服务)。app.on('render-process-gone')/webContents.on('did-fail-load')做兜底与重试。- 关键操作加 try/catch 与用户可见的错误提示,别让应用"静默死掉"。
6.6 架构模式与状态管理
- 主进程 = 后端服务:只做系统能力 + 数据持久化 + IPC 路由。
- 渲染进程 = 前端:用 React/Vue 的 store(Zustand / Pinia)管理 UI 状态,不要把主进程当数据库轮询。
- 跨窗口状态:用主进程做中转,或共享一个轻量 store;避免多个渲染进程各自持有一份真值。
- 模块边界清晰:
main/、preload/、renderer/三目录分离。
6.7 阶段五最佳实践
- ✅ 安全默认项一个都别关;若要关,必须书面记录理由并加补偿措施。
- ✅ 所有 IPC 入参主进程二次校验。
- ✅ 重计算放 utility process,保持 UI 流畅。
- ✅ 测试用 Playwright/Electron-Vitest,Spectron 已弃用不要学。
- ✅ 崩溃上报 + 兜底处理,生产应用必备。
7. 最佳实践速查清单
[安全] contextIsolation: true / nodeIntegration: false / sandbox: true
[安全] 远程内容只用 HTTPS,且关闭 nodeIntegration
[安全] preload 只暴露具体 API,不透传 ipcRenderer
[安全] 主进程对 IPC 入参二次校验(类型/路径/大小)
[安全] 定义严格 CSP,校验 IPC 发送方
[架构] 主进程=后端,渲染进程=前端,preload=安检门
[工程] 新项目用 Electron Forge / electron-vite 起步
[工程] 签名+公证放 CI,证书走 secrets
[工程] 自动更新先提示再重启
[性能] 重活放 utility process,保持版本更新
[质量] Playwright / Electron-Vitest 做测试,crashReporter 监控
8. 常见陷阱 / 踩坑
- 在渲染进程里
require('electron')→ 直接报错或安全漏洞。正确做法:经 preload + contextBridge。 nodeIntegration: true图省事 → 远程 XSS 即 RCE。阶段五安全章详述。- 用
shell.openExternal(untrustedUrl)→ 可被钓鱼/执行危险协议。先白名单校验。 <webview>允许allowpopups且未校验 → 弹出不可信窗口。- 主进程写死大量同步逻辑 → UI 卡顿。改用 utility process / async。
- 自动更新静默重启 → 用户数据丢失。先提示。
- 证书/私钥提交进 git → 安全事故。走 CI secrets。
- 用已弃用的 Spectron → 维护停滞。换 Playwright。
file://协议直接加载 → 触发安全限制。改用自定义协议。- 不更新 Electron 版本 → 已知漏洞未修。定期升级并审 security advisories。
9. 学习资源
- 官方文档(最权威,必看):https://www.electronjs.org/zh/docs/latest
- 安全:https://www.electronjs.org/zh/docs/latest/tutorial/security
- IPC:https://www.electronjs.org/zh/docs/latest/tutorial/ipc
- 沙箱:https://www.electronjs.org/zh/docs/latest/tutorial/sandbox
- 官方示例库:https://github.com/electron/electron-quick-start
- Electron Forge:https://www.electronforge.io/
- electron-vite:https://github.com/alex8088/electron-vite
- electron-builder:https://www.electron.build/
- 社区模板:electron-react-boilerplate、electron-vue-vite(GitHub 搜索最新版)
- 发布博客(跟进版本与安全变更):https://www.electronjs.org/zh/blog
10. 八周学习计划(按周拆解)
| 周 | 主题 | 动手任务 | 验收标准 |
|---|---|---|---|
| 1 | 阶段一:架构 + Hello World | 搭环境,跑通窗口;画一张进程关系图 | 能解释主/渲染/preload 三者职责 |
| 2 | 阶段二:IPC | 用 invoke/handle + preload 做「记事本」读写 | 渲染进程经安全桥读写本地文件 |
| 3 | 阶段三(上):窗口/菜单/托盘 | 做带菜单栏+托盘的最小工具 | 菜单/托盘/快捷键可用 |
| 4 | 阶段三(下):存储/系统集成 | 接入 electron-store + 通知 + 对话框 | 配置持久化,能弹通知 |
| 5 | 阶段四(上):脚手架 + 打包 | 用 Forge/electron-vite 重构,产出安装包 | 三平台安装包生成 |
| 6 | 阶段四(下):自动更新 + 签名 | 配 electron-updater + CI 签名 | 能推送更新、过 Gatekeeper |
| 7 | 阶段五(上):安全 + 性能 | 审一遍安全检查清单,做纵深校验 | 安全默认项全开,主进程校验 |
| 8 | 阶段五(下):测试 + 调试 + 架构 | 写 Playwright E2E,配主进程调试 | 有测试、有崩溃上报、结构清晰 |
一句话总结:Electron 的门槛不在 API 多,而在「进程边界」和「安全意识」。把主进程当后端、渲染进程当前端、preload 当安检门,守住 contextIsolation / sandbox / nodeIntegration 三条线,你就已经超过了大部分生产级 Electron 应用的安全水位。