Electron 一复杂,先失控的往往是 IPC 和状态边界

从进程边界、IPC 合约、状态恢复和更新回滚几个角度,谈 Electron 里真正容易出问题的地方。

Electron 一复杂,先失控的往往是 IPC 和状态边界

很多 Electron 项目在早期都会把主进程当成“应用中枢”。路由、窗口管理、菜单、托盘、更新、文件访问,全都往里塞。

这套做法能快速跑起来,但项目一旦开始变复杂,主进程就会变成一个很脆的协调层。它不是功能太多,而是职责太杂,最后所有问题都会落到同一条消息总线上。

真正的边界是进程,不是文件

Electron 最容易被低估的一点,是主进程和渲染进程之间不是普通模块边界,而是失败模式完全不同的进程边界。

渲染进程可以卡死、重载、崩溃、被浏览器自己回收。主进程则更像系统服务,一旦它的状态设计不稳,所有窗口都会跟着失去上下文。

所以 IPC 不是一个“调用方式”问题,而是一个“你愿不愿意承认两个世界的状态不一致”问题。

IPC 合约要比接口更硬

很多项目刚开始会直接在 IPC 里传对象,哪里方便传哪里。这个阶段没什么问题,直到协议开始长出来。

一旦你有了这些需求,IPC 就不再只是桥:

  • 多窗口共享状态
  • 调用顺序要可重放
  • 某些请求需要幂等
  • 主进程重启后要恢复上下文
  • 不同版本的 renderer 要同时兼容一段时间

这时真正要维护的不是函数签名,而是消息协议。消息类型、版本号、错误码、超时策略,最好都要显式存在。

如果没有这些约束,渲染进程会慢慢把主进程当成远程函数库来用,最后出了问题谁都不知道是调用顺序错了,还是协议已经漂移了。

contextIsolation 之后,问题才开始变得真实

启用 contextIsolationpreload 以后,很多“直接挂全局对象”的老办法都没了。这个变化其实是好事,因为它强迫你认真设计暴露面。

但它也会把隐藏问题暴露出来:

  • 哪些能力该暴露给页面
  • 哪些能力只允许预加载层转发
  • 哪些 API 需要做参数白名单
  • 哪些状态必须单向流动,不能从 renderer 反写回主进程

这不是安全层面的附加题,而是架构本身的一部分。你如果把 preload 当成临时垫片,后面总会有人想绕过去。

状态恢复比打开窗口更重要

Electron 里“重新打开窗口”很容易,“恢复到之前的状态”很难。

用户真正关心的不是有没有窗口,而是这个窗口是否还保留了他们刚才正在做的事。比如:

  • 上次打开的是哪一个文档
  • 表单有没有未保存内容
  • 某个面板展开到什么程度
  • 当前工作区的筛选条件是什么

这类状态如果只存在渲染进程内存里,窗口一崩就没了。更稳的做法是把状态分成三层:

  • 可重建的 UI 状态
  • 必须持久化的业务状态
  • 只在主进程保留的调度状态

这三层不分开,恢复逻辑就会变成一团。

更新不是下载完就结束

很多 Electron 应用的更新问题,表面上看是 autoUpdater 没调对,实际上是更新切换的时机没有定义清楚。

更新真正难的是:

  • 当前窗口有没有未保存内容
  • 是否允许静默重启
  • 是否需要延后切换到新版本
  • 更新失败后怎么回退到旧状态
  • 新旧版本协议是否还能短暂共存

如果这些没有被明确建模,更新流程就会在“看起来成功”和“用户数据丢了”之间来回摇摆。

主进程应该更像调度器

做久了会发现,主进程最合理的角色不是“大脑”,而是调度器。

它负责:

  • 创建和回收窗口
  • 维护少量全局状态
  • 协调 IPC 和生命周期
  • 管住文件、更新和系统级能力

但它不应该吞下业务规则本身。业务状态越往主进程堆,系统越难测试,也越难做恢复。

Electron 项目做到后面,真正能拉开差距的不是“会不会启动一个窗口”,而是你有没有把进程边界、协议边界和状态边界一起设计好。这个分层一旦立住,后面的崩溃恢复、更新、权限控制和多窗口协作才有可能收敛。