Electron 桌面客户端 ASAR 热更新:替换一个文件完成版本切换

Electron 桌面应用的更新一直是个痛点——一次小小的 UI 修改就要让用户重新下载运行 100MB+ 的安装包。本文介绍一种利用 Electron ASAR 打包机制实现"零安装"版本切换的方案:只需替换一个 app.asar 文件,客户端下次启动自动加载新版本。|

一、ASAR 机制与更新思路

1. ASAR 是什么

ASAR(Atom Shell Archive)是 Electron 专用的打包格式,类似于 tar——将所有文件顺序拼接为单个文件,不做压缩但支持随机读取。Electron 通过补丁让 Node.js 的 fsrequire API 将 .asar 文件视为虚拟目录:

1 // 读取 asar 内的文件(像普通目录一样)
2 fs.readFileSync('/path/to/app.asar/dist/main/main.js')
3 
4 // require asar 内的模块
5 require('/path/to/app.asar/node_modules/electron-log')

Electron 打包后的应用目录结构:

1 安装目录/
2 ├── electron.exe + lib/     ← Electron 框架(~70MB)
3 └── resources/
4     └── app.asar             ← 全部业务代码 + npm 依赖(~50MB)

app.asar 内包含:主进程代码(main.js)、IPC 桥接层(preload.js)、渲染进程代码(HTML/JS/CSS)以及全部 node_modules。Electron 启动时固定从 resources/app.asar 加载入口。

参考文档:ASAR Archives | Electron | electron/asar GitHub

2. 更新思路

既然全部业务代码都在 app.asar 一个文件里,替换这个文件就能完成版本切换。但有一个限制——Electron 官方明确说明运行时不能替换正在使用的 asar 文件,类似运行时不能替换 .exe。

解决方案:让客户端优先从外部目录加载 asar,而非安装目录。配合一个后台管理服务(独立于客户端进程),在客户端退出后管理外部 asar 文件。

3. 方案对比

方案更新包大小覆盖范围客户端改动量
仅替换前端资源 ~5MB 仅渲染进程 中等
Bootstrap + 外部目录加载 ~5MB 全部源码,不含 npm 依赖 中等
替换 app.asar(本方案) ~50MB 全部源码 + npm 依赖 极小(~15行)

二、Bootstrap Loader:让 Electron 从外部加载

1. 核心问题

Electron 默认从 resources/app.asar 加载应用,无法配置外部路径。需要一个极小的 bootstrap 入口文件作为跳板。

2. 实现方式

在安装目录的 app.asar 内新增一个 bootstrap.js 作为 Electron 入口(通过 package.jsonmain 字段指定),逻辑仅 ~15 行:

 1 // src/main/bootstrap.ts — 永不变更
 2 import path from 'path';
 3 import fs from 'fs';
 4 
 5 const BASE_DIR = path.join(
 6   process.env.PROGRAMDATA || 'C:\\ProgramData',
 7   'AppUpdater', 'MyApp',
 8 );
 9 const CURRENT_FILE = path.join(BASE_DIR, '@current');
10 const VERSION_RE = /^[\d.\-a-zA-Z]+$/; // 防路径注入
11 
12 try {
13   // 1. 读指针文件,获取当前应加载的版本号
14   const version = fs.readFileSync(CURRENT_FILE, 'utf8').trim();
15   if (!VERSION_RE.test(version)) throw new Error(`invalid: ${version}`);
16 
17   // 2. 拼出版本化路径
18   const externalAsar = path.join(BASE_DIR, version, 'app.asar');
19   if (!fs.existsSync(externalAsar)) throw new Error('not found');
20 
21   // 3. Electron require 支持 asar 虚拟路径,自包含 node_modules
22   const pkg = JSON.parse(
23     fs.readFileSync(path.join(externalAsar, 'package.json'), 'utf8'),
24   );
25   require(path.join(externalAsar, pkg.main));
26 } catch {
27   // 任何异常 → fallback 到安装目录内置的完整代码
28   require(path.join(__dirname, 'dist', 'main', 'main.js'));
29 }

关键设计:

  • 版本化目录:外部目录按版本号组织(2505.1.0/app.asar2505.2.0/app.asar),通过 @current 纯文本指针文件指定激活版本,切换版本只需改一个文件的内容
  • try/catch 兜底:指针文件损坏、asar 文件不存在、任何异常都回退到安装目录内置代码,客户端永远能正常启动
  • 路径注入防护:@current 的内容做正则校验,仅允许数字、字母、点号
  • require 支持 asar:Electron 的 require() 天然支持 asar 虚拟路径,外部 asar 内的 node_modules 也能正确解析,无需额外配置

3. 本地文件布局

 1 外部目录(后台服务管理):
 2 %PROGRAMDATA%\AppUpdater\MyApp\
 3 ├── @current                  ← 指针文件,内容:"2505.3.0"
 4 ├── 2505.2.0\
 5 │   └── app.asar              ← 旧版本(保留便于回滚)
 6 └── 2505.3.0\
 7     └── app.asar              ← 当前激活版本
 8 
 9 安装目录(永远的 fallback):
10 C:\Program Files\MyApp\resources\app.asar
11 ← 含 bootstrap.js + 完整业务代码

三、打包与 CI 适配

1. Webpack 构建配置修改

在现有的 webpack 主进程配置中新增 bootstrap 入口:

 1 // .erb/configs/webpack.config.main.prod.ts
 2 entry: {
 3   main: path.join(srcMainPath, 'main.ts'),
 4   preload: path.join(srcMainPath, 'preload.ts'),
 5   bootstrap: path.join(srcMainPath, 'bootstrap.ts'), // 新增
 6 },
 7 output: {
 8   path: distMainPath,
 9   filename: '[name].js',
10 },

同时修改 release/app/package.json 的入口:

1 { "main": "./bootstrap.js" }

这样 Electron 启动时先执行 bootstrap.js,由它决定加载哪个版本的 main.js

2. CI 流水线新增步骤

electron-builder 打包完成后,从 win-unpacked 中提取 app.asar 并上传:

 1 # 提取 app.asar
 2 $asarPath = ".\release\build\win-unpacked\resources\app.asar"
 3 
 4 # 打包为 zip(仅 app.asar)
 5 Compress-Archive -Path $asarPath -DestinationPath ".\release\publish\${TAG}.zip"
 6 
 7 # 计算 sha256
 8 $hash = (Get-FileHash -Path ".\release\publish\${TAG}.zip" -Algorithm SHA256).Hash
 9 
10 # 生成 version.json
11 @{ version = $TAG; sha256 = $hash } |
12   ConvertTo-Json |
13   Set-Content ".\release\publish\version.json"

发布流程设计为两步:upload(自动) 上传 zip 到归档目录,publish(手动) 覆盖 version.json 到 S3 根目录。后台服务轮询 version.json 感知新版本,下载 zip、校验 sha256、解压到版本目录、原子切换 @current 指针。

3. 整包更新与 asar 更新共存

已有的 electron-updater 整包更新机制不受影响,两者各司其职:

更新类型触发场景更新包
asar 更新 日常迭代(UI、业务逻辑、npm 小版本) ~50MB zip
整包更新 Electron 框架升级、外部工具更新 ~180MB NSIS

总结:利用 Electron 的 ASAR 虚拟文件系统特性,通过一个 ~15 行的 bootstrap loader 实现外部 asar 加载,配合后台管理服务的原子指针切换,以最小的客户端改动实现了完整的应用代码更新。版本化目录设计天然支持零成本回滚,try/catch 兜底确保客户端永远能正常启动。

引用链接:

posted @ 2026-06-04 15:28  唐宋元明清2188  阅读(0)  评论(0)    收藏  举报