完整的全量更新虽然覆盖范围广,但每次都要下载几十 MB 的安装包,既浪费带宽又影响用户体验。在实际产品中,多数业务变更仅涉及界面调整、逻辑修复,真正需要改动底层 Native 模块或 Electron 版本的情况并不频繁。Electron 为此提供了两条更轻量的热更新路径:ASAR 归档更新(主进程和预加载脚本)与渲染层资源热更新(HTML/CSS/JS)。
17.4.1 ASAR 增量更新
Electron 应用在打包后,通常会生成一个 app.asar 文件,它是用 asar 模块将整个 app 目录(包含主进程代码、预加载脚本、资源文件等)打成的归档文件。这种归档不是压缩,而是一种类似 tar 的线性存储,可以快速地按文件名随机读取内容,同时保持了子进程调用的兼容性。因此,很多团队选择通过替换 app.asar 来实现主进程侧的增量更新。
原理与流程
ASAR 增量的核心思路非常简单:把需要更新的 app.asar 提前下载到本地一个临时目录,然后在应用下一次启动时,由主进程将旧的 app.asar 替换为新版。这样做的好处是完全绕过平台安装程序,不需要用户重新运行 .exe 或 .dmg 安装包,也不需要签名校验(签名是在可执行文件层面的,asar 本身只是数据文件)。
一个典型的工作流如下:
- 应用启动后,从服务端拉取最新的版本元信息(JSON),对比当前版本号。
- 如果存在新版
app.asar,通过electron-updater或自写的下载模块,将差分增量包(可选)或完整 asar 包拉取到userData目录下的pending/文件夹。 - 下载完成后进行哈希校验(防篡改和损坏)。
- 关键步骤:通知用户“更新已就绪,重启应用生效”,并调用
autoUpdater.quitAndInstall()或自定义逻辑。在退出前,主进程或一个独立的“更新辅助程序”会将pending/app.asar原子性地重命名为resources/app.asar(覆盖旧文件)。 - 应用重新启动,加载新的
app.asar,完成更新。
原子替换与容错
直接覆盖 app.asar 有风险:如果在替换过程中系统崩溃或进程被杀,可能得到一个损坏的 asar 文件,导致应用无法启动。工业级的做法是双缓冲(交换)策略:保留一个备用的旧版本 app.asar.backup,先用新文件写入到 app.asar.new,然后利用文件系统重命名是原子操作(在 POSIX 系统和 NTFS 上均支持),将新文件更名为 app.asar,旧文件备份。如果启动失败,还可以在 catch 逻辑中自动回退到备份版。
// 主进程:更新 app.asar 示例(简化)
const fs = require('fs');
const path = require('path');
const { app } = require('electron');
function applyAsarUpdate(newAsarPath) {
const resourcesPath = process.resourcesPath;
const asarPath = path.join(resourcesPath, 'app.asar');
const backupPath = asarPath + '.backup';
const tempPath = asarPath + '.new';
// 1. 将新 asar 复制为临时文件
fs.copyFileSync(newAsarPath, tempPath);
// 2. 备份当前 asar
if (fs.existsSync(asarPath)) {
fs.copyFileSync(asarPath, backupPath);
}
// 3. 原子替换
fs.renameSync(tempPath, asarPath);
}
差分 atch 的应用
如果 app.asar 体积较大,每次都下载完整文件并不经济。可以借助 bsdiff、hdiffpatch 等二进制差分工具生成旧版到新版的增量包(patch 文件),客户端下载后在本地将旧 asar 和新 patch 合并为目标 asar。开源工具如 electron-builder 的 nsis 和 differential-update 均支持生成和应用差分更新。需要注意的是,差分合并需要额外的 CPU 和 IO 开销,在低配机器上可能让用户感到卡顿,因此建议配合进度条和后台线程(Worker)执行。
17.4.2 渲染层资源热更新
渲染进程实际上就是一个 Web 页面,因此所有适用 Web 应用的热更新策略,都可以直接搬到 Electron 中使用。无论是 React 的 HMR,还是资源文件(图片、字体、CSS)的版本化替换,都能够在不重启应用的前提下,实现界面的即时生效。这为产品修复紧急 UI 缺陷或临时上架新功能提供了极大的灵活性。
实现方式
1. 基于本地 Web 服务的模块替换
很多团队会在 Electron 应用中内嵌一个本地 HTTP 服务器(通过 express 或内置的 http 模块),渲染进程不再以 file:// 协议加载,而是通过 http://localhost:PORT 访问。这样,所有前端资源(JS、CSS、HTML)就可以像普通 Web 应用一样,由服务端控制缓存、版本号,并支持按模块动态下发。
更新流程如下:
- 应用启动时,主进程启动本地 HTTP 服务,并加载一个单页面入口
http://localhost:8034/index.html。 - 运行时,如果检测到服务端有新版本,主进程通过 IPC 通知渲染进程刷新或替换特定模块。对于现代前端构建产物(如 Webpack/Vite 分包),可以仅下载变动的 chunk 文件并利用
import()动态替换。 - 更激进的方案:直接使用
window.location.reload()刷新页面,此时新资源会自动从本地服务获取,完成“无感”更新。
2. 使用 Service Worker 缓存策略
即使在 file:// 协议下,Service Worker 受到一定限制,但通过 session.defaultSession 配置自定义协议或利用 asar 打包,仍然可以在沙箱内实现类 Service Worker 的拦截器。更常见的做法是结合 webRequest API 拦截资源请求,将远程 CDN 上的最新版本返回给渲染进程。
例如,主进程可以监听特定的请求头,将静态资源代理到本地临时目录或远程服务器:
const { session } = require('electron');
session.defaultSession.webRequest.onBeforeRequest({ urls: ['*://myapp.local/*'] }, (details, callback) => {
const url = details.url;
// 本地路径映射,如果下载了新版则返回新版文件
const localPath = mapToLocalResource(url);
if (fs.existsSync(localPath)) {
callback({ redirectURL: `file://${localPath}` });
} else {
callback({});
}
});
3. 资源包的本地置换
对于完全离线的应用,无法依赖远程 CDN,我们可以将渲染层资源打包成一个 renderer.zip 或 renderer.asar,通过增量更新机制将这个包下载到本地用户数据目录。主进程在创建 BrowserWindow 时,根据本地是否存在新版资源包来选择加载路径:
const resourcePath = fs.existsSync(latestRenderPath) ? latestRenderPath : defaultAsarPath;
win.loadURL(`file://${resourcePath}/index.html`);
一旦新包下载完成,下次创建窗口(或刷新页面)就会加载新界面资源,对用户而言只是界面一闪。
安全与灰度
渲染层热更新虽然快捷,但也要防范 XSS 或中间人攻击。务必启用 HTTPS 和严格的 CSP 策略,并对更新包进行签名校验。在产品层面还可以配合灰度策略:根据用户 ID 或地区下发不同的资源版本,实现 A/B 测试或逐步放量,这在前端工程中已经非常成熟,Electron 完全复用。
17.4.3 综合评估与落地建议
在实际产品中,“ASAR 增量 + 渲染层热更”的组合可以覆盖绝大多数常规变更。当只修改了前端界面或 Node.js 业务逻辑时,直接下发 ASAR 增量包(或替换完整 ASAR)即可,无需用户重新安装。只有当 Electron 内核升级、修改了系统级菜单、托盘,或引入了新的原生模块时,才需要走全量安装包更新流程。
从工程落地的角度,有几点经验值得参考:
- 版本号管理要严格。主进程版本、ASAR 版本、渲染资源版本三者之间必须保持明确的兼容性矩阵。否则可能出现前端调用了主进程新增的 API,但主进程还是旧版,导致崩溃。
- 增量更新需要回退机制。每次更新前备份旧文件,如果新版本频繁崩溃(可监控连续启动失败次数),应用应能自动回滚到上一次的稳定版本。
- macOS 签名问题。只替换
app.asar不会破坏可执行文件的签名,应用仍能正常启动。但如果应用启用了 Hardened Runtime 并限制了文件访问,需确保新 asar 的路径在 entitlements 中被允许。 - 用户感知。无论何种更新,都应通过桌面通知或应用内状态栏给予用户明确的进度反馈。“正在更新”、“更新完成,点击重启”等信息能大幅降低用户的焦虑感,尤其在 ASAR 热更重启时。
增量更新不仅仅是一种分发技术,它更是产品快速迭代和远程修复问题的生命线。一个设计良好的增量更新系统,能让团队在获得媲美 Web 端响应速度的同时,仍然保留桌面应用的稳定与安全。