Electron 中的构建产物 blockmap
前言
electron-builder 构建出来的产物,默认都会有个 .blockmap
文件,这个文件是用来做增量更新的。
什么作用
实现差异检测,增量更新,减少下载量,提高下载速度。
实现逻辑
- 只适用于 nsis, appImage 安装方式
- 在构建阶段输出 blockmap 文件,是当前文件的分块内容描述,文件本身是种文本文件的编码,用于区间下载(指定范围),以及文件复制操作(指定范围)
- 运行阶段, 以 nsis 为例,触发更新时,拉取
latest.yml
后, 会检测即将更新的文件对应的.blockmap
文件, 并尝试使用blockmap
,逻辑如下:- 获取到即将更新的文件的
blockmap
文件 url - 通过 version 替换,得到当前版本对应的
blockmap
文件 url - 检测本地是否有当前版本更新使用的安装文件 (自动更新生成的, 在 Cache 目录下的那个文件)
- 通过对比 oldBlockmap 和 newBlockmap,得到需要更新的文件的分块操作信息, 有两个类型, COPY 和 DOWNLOAD, 分别包含对应操作的分块区间
- 通过得到的分块操作信息,在内存中生成新的安装文件,逻辑就是创建文件流,一部分从老安装文件 COPY, 一部分从网络 DOWNLOAD, 最后生成新的安装文件
- 一切顺利,就得到新的安装包了,通过 blockmap 的方式,实现了增量下载,按需下载能力,是静态服务器支持
multipart/byteranges
请求的头 - 上述过程中,还有很多 sha256 的校验,以及异常处理, 如果发生失败, 都会走到全量下载方式上
- 获取到即将更新的文件的
几个注意事项:
- blockmap 是
electron-builder
的更新的优化方案, 不是 electron 默认支持的 autoUpdater 的更新方案 - NSIS 的话,记得要上传安装文件和安装文件对应的
blockmap
文件。并确保blockmap
文件各版本的 url 是可访问的 - Mac 生成 zip 的时候,会强制生成
blockmap
文件,不可禁用,但又不会被使用 - NSIS 如果不使用
blockmap
, 可在electron-builder
中配置"nsis":{ "differentialPackage": false}
源码部分
- electron-builder/packages/electron-updater/src/util.ts at 869c7e4652a5d5a3562e25723d6cedd622ab657b · electron-userland/electron-builder: 通过最新的更新地址,生成当前版本对应的 blockmap 文件地址, 然后分别下载。
- nsisUpdater 里面, 先尝试去下载 blockmap , 然后通过
latest.yml
可以得到即将更新版本的blockmap
和 当前版本的blockmap
, 以及当前版本被缓存过的zip
包地址,通过三者,能计算获得新的zip
- 这样的情况,版本号就不能乱变动了,不然本地缓存的上次更新的文件,不一定是当前的版本对应的文件。
- 通过
GenericDifferentialDownloader
去调用download
, 初始化的时候,传递了downloadOptions
, 包括了oldFile
, 老的缓存文件,newFile
下载目标目录等。 - 父类
DifferentialDownloader
中实现了下载electron-builder/packages/electron-updater/src/differentialDownloader/DifferentialDownloader.ts at master · electron-userland/electron-builder, 并且给了两个blockmap
parse 过的数据。blockmap 也有version
区分的, 这就要看构建任务了。- 计算两个
blockmap
, 得出操作类型,COPY
还是DOWNLOAD
, electron-builder/packages/electron-updater/src/differentialDownloader/downloadPlanBuilder.ts at 869c7e4652a5d5a3562e25723d6cedd622ab657b · electron-userland/electron-builder, 新的文件,就是 copy + download 组合起来的, 需要和latest.yml
中的 size 对比,不一致,就还走全量下载。 - 调用
doDownloadFile
去下载部分区间内容 electron-builder/packages/electron-updater/src/differentialDownloader/ProgressDifferentialDownloadCallbackTransform.ts at master · electron-userland/electron-builder
- 计算两个
- 最终就是基于这三个文件,下载得到了新的
.exe
或者.zip
包
构建时候
如果是第一次构建,这个 .blockmap
生成就代表了这个对应文件的差分部分的内容。
运行阶段
- 本地要有 blockmap 文件, 然后有远程的
blockmap
文件 - 计算出要下载远程的
zip
的哪一部分,下载文件时,是可以下载特定区间的,比如:文件内容是 a 到 z,在请求的时候,我可以通过 header 告知,下载第 2 个位置到第 4 个位置。下载完成后,内容就是: bcd, 所以下载的是新构建内容的部分。使用的响应请求头是multipart/byteranges
- 合并内容,得到了下载的部分内容后,应该要本地再组装才能还原成一个完整的安装文件
- 源码中,我们可以看到
AppImageUpdater.ts
和NsisUpdater.ts
会使用到, 会使用到差异下载, electron-builder/packages/electron-updater/src/differentialDownloader at master · electron-userland/electron-builder
如何禁用
这个流程还是挺麻烦的, 并且不支持 Mac, 打包构建它的时候,还挺花时间的。
Remove Block Map · Issue #2900 · electron-userland/electron-builder
- nsis 里面
differentialPackage
, electron-builder/packages/app-builder-lib/src/targets/nsis/NsisTarget.ts at 869c7e4652a5d5a3562e25723d6cedd622ab657b · electron-userland/electron-builder - dmg 里面
writeUpdateInfo
, electron-builder/packages/dmg-builder/src/dmg.ts at master · electron-userland/electron-builder
electron-builder.yml
文件的定义:electron-builder/packages/app-builder-lib/src/configuration.ts at master · electron-userland/electron-builder
mac 的 zip 需要 patch 代码
源码没有对应的配置项。需要需要关闭的话, 修改electron-builder/packages/app-builder-lib/src/targets/ArchiveTarget.ts at master · electron-userland/electron-builder ,注释掉:
updateInfo = await createBlockmap(artifactPath, this, packager, artifactName)