Electron 架构总览:主进程、渲染进程与进程间通信
Electron 经常被一句话概括成「用 Web 技术写桌面应用」,但真正落到工程上,如果只停留在这句话,很容易在安全、性能和架构上踩坑。
这一篇先不讲打包和配置,而是想搞清楚几件事:Electron 是怎么把 Chromium 和 Node 拼在一起的、主进程和渲染进程各自负责什么,以及两者之间的通信在工程上应该怎么设计。
1. Electron 的三块拼图:Chromium + Node + 主进程外壳
从架构角度看,Electron 可以粗略拆成三块:
- Chromium 渲染引擎
- 提供浏览器级别的渲染能力(HTML/CSS/JS、DevTools、Web 标准等);
- 每个窗口(
BrowserWindow)背后都有一个对应的渲染进程。
- Node.js 运行时
- 提供文件系统、进程管理、网络等能力;
- 在主进程和(可选的)渲染进程里提供 JS 侧的系统访问能力。
- Electron 主进程
- 管理应用生命周期(启动、退出、单实例等);
- 创建和管理
BrowserWindow; - 负责与操作系统集成(菜单、托盘、通知、协议、文件关联等)。
可以用一句话概括:
- 主进程更像「系统服务 + 窗口管理器」,渲染进程更像「浏览器页面」,两者共享 Node 能力并通过 IPC 协作。
理解这一层,有助于后面在安全和职责划分上不把所有逻辑都堆进某一个进程。
2. 主进程:应用的“大脑”和系统入口
2.1 主进程的职责
在 Electron 应用里,主进程主要承担这些角色:
- 应用生命周期管理:
- 响应
app.on('ready')、app.on('window-all-closed')等事件; - 控制应用何时退出、单实例锁等。
- 响应
- 窗口与视图管理:
- 创建
BrowserWindow,控制窗口尺寸、位置、样式; - 维护窗口列表、聚焦状态、多窗口通信等。
- 创建
- 与操作系统集成:
- 菜单(Menu)、托盘(Tray)、通知(Notification)、Dock 图标;
- 文件/URL 协议处理(通过
app.setAsDefaultProtocolClient等)。
- 封装本地能力:
- 作为「本地 API 网关」,对外暴露受控接口给渲染进程使用。
在工程实践中,一个健康的模式是:
- 主进程只做「需要系统权限」和「需要跨窗口协调」的事;
- 不在主进程里堆业务 UI 逻辑,更不直接处理复杂渲染相关代码。
2.2 BrowserWindow 与 WebContents
每个 BrowserWindow 底下,有一个对应的 WebContents:
BrowserWindow负责窗口本身的行为(大小、位置、最大化/最小化等);WebContents负责加载和运行具体的页面(URL 或本地 HTML)。
主进程通过这些对象可以:
- 给窗口发送消息;
- 监听页面加载、导航、崩溃等事件;
- 在需要时对某些行为做拦截或限制。
3. 渲染进程:前端应用的运行场所
3.1 渲染进程的职责
渲染进程本质上就是:
- 在 Chromium 里跑的一个「Web 应用」,负责:
- UI 渲染(React/Vue/Svelte/原生 DOM);
- 用户交互与状态管理;
- 调用受限的本地 API(通过 IPC 或预暴露接口)。
在安全配置合适(例如 nodeIntegration: false, contextIsolation: true)的前提下,渲染进程应该尽量:
- 像普通 Web 前端一样工作;
- 不直接拥有全面的 Node 能力,而是通过主进程接口访问系统资源。
3.2 多个渲染进程与多窗口
每个 BrowserWindow 默认对应一个独立的渲染进程:
- 多窗口应用 = 多个渲染进程;
- 每个渲染进程之间不能直接访问彼此的内存,需要通过主进程或其他通道通信。
在工程层面:
- 需要决定哪些状态放在每个窗口内部管理;
- 哪些状态由主进程或外部后端服务来协调。
4. 进程间通信(IPC):主进程与渲染进程如何对话?
4.1 为什么不能「直接调用」?
主进程和渲染进程不在同一个 JS 运行上下文中:
- 不能直接调用对方的函数;
- 不能直接读写对方的变量。
为了让两边协作者感又不破坏隔离,Electron 提供了 IPC 机制:
- 主进程侧:
ipcMain; - 渲染进程侧:
ipcRenderer(在安全模式下一般通过 preload 暴露封装后的接口)。
4.2 IPC 的基本模式
可以抽象成两种常见模式:
- 事件通知
- 渲染进程向主进程发送某个事件(例如「用户点击了 X」);
- 主进程根据事件做响应(例如「打开某个系统窗口」)。
- 请求-响应
- 渲染进程发起一个请求(例如「读取某个配置文件」);
- 主进程执行操作后返回结果。
工程实践上更推荐统一为「请求-响应风格」的调用接口,避免到处散落着难以追踪的事件名。
5. 推荐的通信与安全模式:preload + contextBridge
直接在渲染进程里开启 Node 能力(nodeIntegration: true)虽然方便,但引入的风险非常大:
- 一旦页面存在 XSS 漏洞,就相当于把本地 Node 权限暴露给任意脚本;
- 桌面应用退化成「带本地 Root 权限的浏览器标签页」。
更推荐的模式是:
- 在
BrowserWindow配置中:nodeIntegration: false;contextIsolation: true;- 指定一个预加载脚本(
preload)。
- 在 preload 脚本里:
- 使用
contextBridge.exposeInMainWorld,挂一个有限的 API 到渲染进程全局对象上; - 内部通过
ipcRenderer与主进程通信。
- 使用
这样可以做到:
- 渲染进程只能通过预定义好的 API 调用本地能力;
- 主进程可以在这些 API 中加入权限检查、参数验证、日志审计等逻辑。
从架构角度看,相当于:
- 主进程 + preload 组成一个「本地服务层」,渲染进程只是前端客户端。
6. 与 Web / 后端服务的关系:Electron 并不是「全栈替代品」
在设计 Electron 应用架构时,一个常见误区是把所有后端逻辑都塞进主进程或渲染进程。
更健康的分工通常是:
- 后端服务
- 处理核心业务逻辑、多用户协作、持久化数据等;
- 与桌面应用解耦,方便以后扩展到 Web 或移动端。
- Electron 主进程
- 负责本地环境相关的增强(文件系统、原生菜单、托盘、协议处理);
- 封装成一组「本地 API」,通过 IPC 暴露给渲染进程。
- 渲染进程
- 用前端技术栈实现 UI/交互;
- 通过 HTTP/WebSocket 调用远端后端服务,通过本地 API 调用主进程能力。
这样可以兼顾:
- 桌面版的本地体验;
- 服务端的伸缩能力与多端复用;
- 架构上的清晰边界。
7. 小结:理解 Electron 架构时要抓住的几个点
这一篇可以压缩成几条关键结论:
- Electron 把 Chromium 和 Node 拼在一起,但通过「主进程 / 渲染进程」划分了职责:前者偏系统与窗口管理,后者偏 UI 与交互;
- 进程间通信通过 IPC 完成,推荐用 preload + contextBridge 暴露受控本地 API,而不是在渲染进程里直接开启 Node 能力;
- 在更大的系统中,Electron 通常作为桌面壳存在,核心业务逻辑仍然由独立后端服务负责,桌面应用通过「远端后端 + 本地 API」两层能力组合完成工作。
后面如果继续展开 Electron 相关内容,可以在这个架构基础上分别深入安全模型、性能与资源管理、自动更新与配置管理,以及和 IDE / Web 工具的集成方式。