要说 electron 的自动更新有多难,上篇的版本管理服务器只是开胃菜。真正难啃的是这篇。全文分成 electron 14,electron-updater, electron-builder 三部分。
为什么要用 14 版本?因为低于 14 版本用不了 electron-builder 的 NSIS 打包。并且需要把 nodeJs 升级到 12 版本以上。从低版本过来的朋友会发现好多报错,因为 14 做了一篮子改变,改的改删的删, 具体看 Electron 14.0.0 | Electron .
最明显的改变是 remote 的引用方式。旧版是 require('electron').remote ,而新版把 remote 移到了独立的模块 @electron/remote, 使用前必须在main process 中初始化。
// 替换为:
const { BrowserWindow } = require('@electron/remote')
// 在主进程中:
require('@electron/remote/main').initialize()
// 必须有这句,才能在 renderer process 中使用 remote
require("@electron/remote/main").enable(win.webContents)
安装 @electron/remote
npm install @electron/remote -S
使用方法详情 重大更改 | Electron
第二个大变是 webview。原来通过 preload 往网页中注入 js,跟网页共享一个上下文。新版中注入的 js 在单独的上下文,不可跟网页互相调用。
替代 electron 自带的 autoUpdater。后者支持 Squirrel 更新方式,而前者支持 Squirrel、NSIS 等方式。NSIS 有更灵活的可定制性,后面会讲到。根据官方的声明,将来逐步摒弃 Squirrel 转而支持 NSIS. 两者详细差异对比 Auto Update - electron-builder。
两者的 API 大致相同。electron-updater 无需调用 setFeedURL, 而是把 url 写在 package.json.
配置 package.json
修改 url 为版本服务器地址. build 为 top-level 属性。
"build": {
"win": {
"publish": {
"provider": "generic",
"url": "http://your-domain/update/win",
"useMultipleRangeRequest": false
}
}
}
加入自动更新代码
const { autoUpdater } = require("electron-updater")
const log = require('electron-log')
// log 初始化,省略
// 检查更新
autoUpdater.logger = log
// 当可更新时在系统通知栏提示
autoUpdater.checkForUpdatesAndNotify()
// 有用户触发安装更新
autoUpdater.quitAndInstall(true, true)
调用 autoUpdater.checkForUpdatesAndNotify() 将检查 http://your-domain/update/win/latest.yml, win 可写成 win32 或 win64,分别代表 32 和 64 位操作系统。有可用更新则自动下载。不想自动下载可设置 autoUpdater.autoDownload = false,然后用 appUpdater.downloadUpdate(cancellationToken) 手动下载。
退出软件时安装新版本。安装包在这里找到 C:\Users\linglq\AppData\Local\fchp-updater\pending。fchp 换成你的 app 的名字。
详细使用方法参考 Auto Update - electron-builder
将 APP 打包成安装包。支持自动更新。选择 NSIS 还可以自定义安装过程。因为实际的项目中需要检测是否安装了某个第三方软件,没有则启动安装,有则忽略。它都能满足。
安装 electron-builder
npm install electron-builder -D
修改 package.json
// top-level
"build": {
"appId": "appID",
"win": {
"target": "nsis",
"icon": "src/assets/app.ico",
"publish": {
"provider": "generic",
"url": "http://your-domain/update/win",
"useMultipleRangeRequest": false
}
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"perMachine": true,
"deleteAppDataOnUninstall": true,
"include": "build/installer.nsh"
},
"extraResources": [
{
"from": "resources/aria2",
"to": "aria2"
}
]
},
"script": {
"pack": "electron-builder --dir",
"dist": "electron-builder"
}
对上面的配置逐一讲解。
开始打包
npm run pack 只生成文件和目录,不打包成 exe
npm run dist 打包成最终的安装文件,以及.blockmap文件
接下来看看是如何做到检测第三方软件的。
build/installer.nsh
!macro customInstall
ReadRegStr $0 HKLM "SOFTWARE\GPL Ghostscript\9.27" "GS_LIB"
StrCmp $0 "" 0 skip_ghost_script
File /oname=$PLUGINSDIR\gs927w32.exe "${BUILD_RESOURCES_DIR}\gs927w32.exe"
ExecWait '"$PLUGINSDIR\gs927w32.exe" /sw'
skip_ghost_script:
;;
ReadRegStr $0 HKLM "SOFTWARE\GraphicsMagick\Current" "Version"
StrCmp $0 "" 0 skip_graphic_magick
File /oname=$PLUGINSDIR\GraphicsMagick-1.3.33-Q8-win32-dll.exe "${BUILD_RESOURCES_DIR}\GraphicsMagick-1.3.33-Q8-win32-dll.exe"
ExecWait '"$PLUGINSDIR\GraphicsMagick-1.3.33-Q8-win32-dll.exe" /sw'
skip_graphic_magick:
;;
!macroend
大抵的意思是 ReadRegStr 读取注册表字段,判断软件是否存在。没有就用 ExecWait 启动安装,并且等待安装完成。有就略过。
第三方 exe、msi 放在 build 目录下。 对于 msi 引用方式稍微改变一下
ExecWait '"msiexec" /i "$PLUGINSDIR\extramsi.msi" /passive'
更详细的 NSIS 语法看 https://github.com/NSIS-Dev/Documentation/tree/master/Reference
两者都是将源码打包成 asar 格式。不同的是,前者生产的文件更大。同一份代码,electron-builder 打包出来的程序有 200M,而 electron-packager 只有 100M。相差近一倍。
还有更大的不同在于写文件方面。electron-packager 生成的是绿色包,可在程序所在目录下面任意读写文件。而 electron-builder 不一样,生成的安装包安装到系统以后,不能再程序目录写入文件或新建目录,否则报“权限不足”的错误。那缓存和数据文件应该怎么保存呢?正确的方法是写到用户的数据目录。
const logDir = path.join(app.getPath('userData'), 'logs')
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
以笔者为例,文件保存到了 C:\Users\linglq\AppData\Roaming\fchp\logs