Electron 架构设计
Electron 是基于 Chromium + Node.js 的跨平台桌面应用框架,用 Web 技术(HTML/CSS/JS)构建原生桌面应用。
🏗️ 整体架构
┌─────────────────────────────────────────────────────┐
│ Electron 应用 │
│ │
│ ┌────────────────────────────────────────────────┐ │
│ │ Main Process (Node.js) │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │ │
│ │ │ app │ │ Browser │ │ ipcMain │ │ │
│ │ │ 生命周期 │ │ Window │ │ 消息处理 │ │ │
│ │ │ │ │ 窗口管理 │ │ │ │ │
│ │ └──────────┘ └──────────┘ └───────┬───────┘ │ │
│ │ │ │ │
│ └────────────────────────────────────┼────────────┘ │
│ │ │
│ ┌──────────────────┼────────┐ │
│ │ Preload Script│ │ │
│ │ contextBridge │ │ │
│ │ (安全桥接层) │ │ │
│ └────────┬─────────┘ │ │
│ │ │ │
│ ┌──────────────────────────▼───────────────────┐ │
│ │ Renderer Process (Chromium) │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────────────┐ │ │
│ │ │ HTML │ │ CSS │ │ JavaScript │ │ │
│ │ │ DOM │ │ Styles │ │ Web APIs │ │ │
│ │ └─────────┘ └─────────┘ └─────────────────┘ │ │
│ │ │ │
│ └───────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────┘
🔑 核心设计思想
1. 继承 Chromium 的多进程模型
Electron 直接继承了 Chrome 浏览器的多进程架构:
| 进程类型 | 对应 Chrome | 职责 |
|---|---|---|
| Main Process | Browser Process | 管理窗口、生命周期、系统 API |
| Renderer Process | Renderer Process | 渲染网页、处理用户交互 |
| Utility Process | Utility Process | 运行独立任务(Node.js 子进程) |
设计哲学:每个窗口一个独立进程,一个窗口崩溃不影响其他窗口。这直接复用了 Chromium 成熟的进程隔离方案。
代价:每个窗口都运行一个完整的 Chromium 实例,内存开销大。
2. Node.js + Chromium 双引擎捆绑
每个 Electron 应用 = Chromium(渲染引擎)+ Node.js(后端运行时)
| 组件 | 作用 | 为什么捆绑 |
|---|---|---|
| Chromium | 渲染 HTML/CSS/JS,提供 Web API | 保证跨平台渲染完全一致 |
| Node.js | 文件系统、网络、子进程等系统 API | 让前端能调用后端能力 |
| V8 | JavaScript 引擎 | Chromium 和 Node.js 共享 |
权衡:应用体积 100-200MB(每个应用都带一份 Chromium),但换来的是渲染一致性 — 同一应用在 Windows/macOS/Linux 上看起来完全一样。
3. 三层进程安全模型
信任级别: 高 ──────────────────────────────────── 低
Main Process Preload Script Renderer Process
(Node.js) (Isolated World) (Chromium 沙箱)
完全信任 部分信任 不信任
可访问所有 contextBridge 只能通过暴露的
系统 API 安全暴露 API API 通信
安全边界:
- Main Process:完全信任,可访问所有系统资源
- Preload Script:可访问 Node.js API,但通过
contextBridge选择性暴露给 Renderer - Renderer Process:运行不可信的 Web 代码,无 Node.js 访问权限
4. IPC 消息传递模型
Main Process 和 Renderer Process 不能直接通信,必须通过 IPC(Inter-Process Communication):
Renderer Process Main Process
│ │
│ ipcRenderer.invoke('channel') │
│ ───────────────────────────────> │
│ │ ipcMain.handle('channel')
│ │ 执行操作
│ Promise<result> │
│ <─────────────────────────────── │
│ │
│ ipcRenderer.send('channel') │
│ ───────────────────────────────> │ (单向,无返回值)
│ │
│ webContents.send('channel') │
│ <─────────────────────────────── │ (Main → Renderer)
🧩 核心组件
Main Process 模块
| 模块 | 作用 |
|---|---|
app | 应用生命周期(ready/quit/activate 等事件) |
BrowserWindow | 创建和管理应用窗口 |
ipcMain | 处理来自 Renderer 的 IPC 消息 |
Menu | 原生应用菜单和上下文菜单 |
Tray | 系统托盘图标 |
dialog | 原生文件选择/消息对话框 |
Notification | 系统通知 |
shell | 用默认应用打开 URL 或文件 |
globalShortcut | 注册全局快捷键 |
powerMonitor | 监听电源状态变化 |
screen | 获取屏幕信息 |
session | 管理 Cookie、缓存、代理 |
webContents | 控制 WebView 内容(加载 URL、执行 JS) |
Renderer Process 模块
| 模块 | 作用 |
|---|---|
ipcRenderer | 向 Main Process 发送 IPC 消息 |
webFrame | 控制当前网页的渲染行为 |
contextBridge | 安全地将 API 从 Preload 暴露到 Renderer |
desktopCapturer | 捕获屏幕/窗口内容(屏幕共享) |
| Web APIs | 完整的 Chromium Web API(DOM、Fetch、WebGL 等) |
Preload Script
Preload 是连接 Main 和 Renderer 的安全桥梁:
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
// 暴露安全的 API 给 Renderer
contextBridge.exposeInMainWorld('electronAPI', {
// 方法一:invoke(请求-响应)
getVersion: () => ipcRenderer.invoke('get-version'),
// 方法二:send + on(事件)
onUpdateReady: (callback) => {
ipcRenderer.on('update-ready', (_event, data) => callback(data));
},
// 方法三:暴露只读数据
platform: process.platform,
isDev: !app.isPackaged
});
关键安全规则:
- 永远不要暴露
ipcRenderer本身给 Renderer - 永远不要暴露
require、fs、child_process等原始 API - 只暴露最小必要的功能
🔄 IPC 通信详解
invoke / handle(推荐,请求-响应)
// Main Process
const { ipcMain } = require('electron');
ipcMain.handle('read-file', async (_event, filePath) => {
const content = await fs.promises.readFile(filePath, 'utf-8');
return content;
});
// Renderer Process (通过 Preload 暴露)
const content = await window.electronAPI.readFile('/path/to/file');
send / on(单向,Renderer → Main)
// Main Process
ipcMain.on('open-settings', (_event) => {
createSettingsWindow();
});
// Renderer Process
window.electronAPI.openSettings();
webContents.send(单向,Main → Renderer)
// Main Process
mainWindow.webContents.send('download-progress', { percent: 75 });
// Renderer Process (通过 Preload 监听)
window.electronAPI.onDownloadProgress((data) => {
progressBar.style.width = `${data.percent}%`;
});
📐 进程模型详解
窗口进程隔离
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Main Process │ │ Renderer #1 │ │ Renderer #2 │
│ (1个,共享) │ │ (窗口A) │ │ (窗口B) │
│ │ │ 独立进程 │ │ 独立进程 │
│ app │ │ HTML/CSS/JS │ │ HTML/CSS/JS │
│ BrowserWindow │ │ Preload │ │ Preload │
│ ipcMain │ │ ipcRenderer │ │ ipcRenderer │
└─────────────────┘ └─────────────────┘ └─────────────────┘
- Main Process 始终只有 1 个
- 每个 BrowserWindow 有独立的 Renderer Process
- 窗口间通信必须经过 Main Process 中转
Utility Process
Electron v24+ 引入的 Node.js 子进程,用于 CPU 密集型任务:
const { utilityProcess } = require('electron');
const child = utilityProcess.fork('worker.js');
child.postMessage({ task: 'heavy-computation' });
child.on('message', (result) => {
console.log('Result:', result);
});
🛡️ 安全架构
安全最佳实践(20 条规则)
| # | 规则 | 原因 |
|---|---|---|
| 1 | 只加载 HTTPS 内容 | 防止中间人攻击 |
| 2 | 启用 contextIsolation: true | 隔离 Preload 和 Renderer |
| 3 | 禁用 nodeIntegration: true | 防止 Renderer 访问 Node.js |
| 4 | 设置 CSP | 限制资源加载来源 |
| 5 | 使用 Preload + contextBridge | 安全暴露 API |
| 6 | 验证所有 IPC sender | 防止伪造消息 |
| 7 | 不要暴露 ipcRenderer 原始对象 | 防止任意 IPC 调用 |
| 8 | 使用 setWindowOpenHandler | 控制新窗口行为 |
| 9 | 禁用 remote 模块 | 已弃用,不安全 |
| 10 | 验证 webview 标签 | 防止嵌入恶意内容 |
Context Isolation(上下文隔离)
┌──────────────────────────────────────────┐
│ Renderer Process │
│ │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ Main World │ │ Isolated World│ │
│ │ (网页代码) │ │ (Preload) │ │
│ │ │ │ │ │
│ │ window.electronAPI ← contextBridge │
│ │ (只能调用暴露的API) (可访问Node.js)│ │
│ │ │ │ │ │
│ │ ✗ require │ │ ✓ require │ │
│ │ ✗ fs │ │ ✓ fs │ │
│ │ ✗ child_process│ │ ✓ child_process│ │
│ └────────────────┘ └────────────────┘ │
└──────────────────────────────────────────┘
🔧 打包与分发
electron-builder vs Electron Forge
| 工具 | 特点 | 适用场景 |
|---|---|---|
| electron-builder | 成熟稳定,功能丰富,支持自动更新 | 生产级应用,需要自动更新 |
| Electron Forge | 官方推荐,集成 Webpack/Vite | 新项目,标准化流程 |
支持的打包格式
| 平台 | 格式 |
|---|---|
| macOS | .dmg, .pkg, .app(MAS) |
| Windows | .exe(NSIS), .msi |
| Linux | .AppImage, .deb, .rpm, .snap |
自动更新机制
┌──────────┐ ┌──────────────┐ ┌──────────┐
│ 用户设备 │ │ 更新服务器 │ │ 发布渠道 │
│ │ │ (GitHub/S3) │ │ │
│ 启动应用 │ ──> │ 检查版本 │ <── │ 推送新版本│
│ │ │ │ │ │
│ 下载更新 │ <── │ 返回更新信息 │ │ │
│ │ │ │ │ │
│ 重启应用 │ │ │ │ │
└──────────┘ └──────────────┘ └──────────┘
🆚 与 Tauri 架构对比
| 维度 | Electron | Tauri |
|---|---|---|
| 渲染引擎 | 捆绑 Chromium | 系统 WebView |
| 后端语言 | Node.js (JS) | Rust |
| 进程模型 | Main + Renderer + Utility | Core + WebView |
| IPC | ipcMain/ipcRenderer | invoke() + Events |
| 安全模型 | 需手动配置(默认开放) | Capability-based(默认安全) |
| 二进制体积 | 100-200MB | 3-10MB |
| 内存占用 | 150-300MB | 30-50MB |
| 渲染一致性 | 完全一致(同一 Chromium) | 跨平台有差异 |
| 构建速度 | 快(JS 工具链) | 慢(Rust 编译) |
| 移动端 | 无 | iOS + Android |
| 生态成熟度 | 极成熟(10 年+) | 成长期 |
| 学习曲线 | 低(纯 JS/TS) | 中(需 Rust) |
🔗 相关链接
- JavaScript Node.js Chromium 跨平台开发 桌面应用 Tauri
- Electron 官方文档
- 进程模型
- 安全指南
- electron-builder
- Electron Forge
📝 个人备注
(留白,供后续补充学习心得)