基于Electron开发了桌面应用程序,最后免不了要做安装包,还少不了“在线升级”功能。Electron号称支持自动升级,但真到动手做的时候,才发现并没有官方文档上说得那么简单。最近在网上看了不少文章,反复尝试,千头万绪,其中不乏过时信息的干扰,搞得我晕头转向。正当灰头土脸之际,看到这篇文章,思路逐渐清晰。
我也来总结一下吧,先梳理一个脑图:
再考虑两个关键约束:
- GitHub在国内访问很不稳定,会影响升级成功率。因此基于GitHub的发布方式不能用。
- electron-forge打出来的安装包,安装时无法修改安装路径。electron-forge的底层是electron-packager,所以electron-packager用起来更复杂,也不在选择之列。剩下就只有electron-builder了。
最后我的选择是脑图中用绿色标出来的路径。实际上,我跑通了两套方案(注:仅在Windows上试验,因为买不起苹果电脑……),不管是electron-forge打出来的包还是electron-builder打出来的包,都能走通自动升级流程。下面就来分别介绍一下,大家各取所需吧。
方案一:electron-forge + electron自带autoUpdater + 自己部署服务器
electron-forge是Electron官方推荐的打包工具,如果不预先了解它的缺陷(比如不能自定义安装路径、安装过程只有一个简陋的GIF动画——这种“免安装过程”的简易安装方式居然被解说成“优势”,见仁见智吧……),很容易被带坑里。但如果你不介意这些缺陷,electron-forge倒也是一种不错的选择,很容易上手。依据官网的“快速入门”搭建Electron演示程序,并且按照文档最后一节“打包并分发您的应用程序”的步骤,为应用项目加入必要的electron-forge配置。
执行npm run make打出安装包,输出在项目文件夹的out子目录。接下来,怎么自己搭建服务器来测试呢?如前文所述,我们不想依赖GitHub来发布。官网推荐了electron-release-server这个开源服务,它还提供了一个后台Web系统来管理版本发布,用起来很方便。
说白了,为了让electron-forge打包的程序能走通自动升级,服务端需要部署两个文件:新版本程序的.nupkg文件和RELEASES文件。这两个文件可以在安装包exe的相同路径下找到。在electron-release-server上部署时,只需要上传.nupkg文件,系统会自动生成匹配的RELEASES文件。然而,实际的测试结果并不愉快😓 系统自动生成的RELEASES文件内容为:
0dbe26b8ef3b8b910a8705bc3e554c3ac3383660 /download/flavor/default/1.5.1/windows_32/autoupdatertester-1.5.1-full.nupkg 343892329
在客户端解析这个文件时抛出了异常:
fatal: Finished with unhandled exception: System.AggregateException: 发生一个或多个错误。 ---> System.Exception: Filename can either be an absolute HTTP[s] URL, *or* a file name在 Squirrel.ReleaseEntry.ParseReleaseEntry(String entry)
对比本地打包时生成的RELEASES文件内容(如下),差异是升级包文件名前有多余的路径:
0DBE26B8EF3B8B910A8705BC3E554C3AC3383660 autoupdatertester-1.5.1-full.nupkg 343892329
尝试过几次修改electron-release-server的服务端配置(据说修改config/local.js 里的appUrl指向一个具体的http地址即可),均未成功!不想浪费太多时间!不就是在服务器上部署两个文件嘛,换成Minio来做吧(灵感来自于这篇文章)。
Minio是实现了S3协议(S3:Simple Storage Service,简单存储服务)的简易服务器,用起来非常方便!搭建步骤如下:
- 从https://min.io/download下载Server程序(minio.exe)和Client程序(mc.exe),并且存放在本地硬盘上,如D:\minio-home下
- 在minio-home文件夹下创建minio-data子文件夹,再在minio-data下创建test-bucket1子文件夹
- 在控制台执行如下命令即可将服务运行起来,还可以看到一些关键信息输出:
minio server ./minio-data
4. 在浏览器里打开http://127.0.0.1:9000,用户名和密码都输入为minioadmin,登录成功后,点击“test-bucket1”右侧的Manage按钮,然后将Access Policy从Private改成Public
5. 可以在D:\minio-home\minio-data\test-bucket1下放一个index.html,然后尝试在浏览器里访问http://127.0.0.1:9000/test-bucket1/index.html,如果打开成功,恭喜你,Minio服务搭建成功了!
终于来到了激动人心的时刻!我们开始测试自动升级:
- 确保main.js中autoUpdater指向上文搭建的服务器地址:autoUpdater.setFeedURL('http://127.0.0.1:9000/test-bucket1') 注:后续在线上部署正式环境之后,这里也要记得改一下。
- package.json中“version”设置为初始版本,假定为1.5.0
- 执行npm run make进行打包
- 运行out\make\squirrel.windows\x64\xxx-1.5.0 Setup.exe,将程序安装到本地硬盘
- 将package.json中“version”改为高版本,比如1.5.1
- 再次执行npm run make进行打包
- 将out\make\squirrel.windows\x64\下的xxx-1.5.1-full.nupkg和RELEASES两个文件拷贝到D:\minio-home\minio-data\test-bucket1目录下
- 运行低版本v1.5.0演示程序,过几秒钟,如果看到下面这个弹窗,恭喜你,测试成功啦!(如果不成功,需要把package.json的”version”改回1.5.0,调整代码后重新打包、安装后再测试……)
GitHub完整演示代码:
https://github.com/luqiming666/AutoUpdater-forge
补充几点说明:
- 客户端使用Electron自带的autoUpdater来处理自动升级,即const { autoUpdater } = require('electron')
- 自动升级功能不可以在VS Code中调试,因为这个功能依赖的升级组件只有在打包后才会带上,于是只能通过electron-log写日志的方式来排查问题。日志文件位于:%AppData%\{app name}\logs\main.log。在演示程序的安装目录下也有几个Squirrel相关的日志文件,可以查看到更多细节。
- electron-is-dev组件可以很方便地检测当前程序执行在调试模式还是正式运行模式。
- 需要安装electron-squirrel-startup组件,并且在main.js代码的顶部放置下面这行代码,以创建桌面快捷方式:
if (require('electron-squirrel-startup')) return;
方案二:electron-builder + electron-updater的autoUpdater + 自己部署服务器
electron-builder是另一种非常强大的打包工具,它支持Windows、macOS和Linux,以及很多种打包格式,在Windows上支持NSIS安装包——允许各种定制。总之,它的官方文档值得仔细一读。
我们还是可以依据Electron官网的“快速入门”来搭建演示程序,只是不再执行electron-forge的打包步骤。接下来,我们侧重介绍“自动升级”相关的package.json配置。package.json中的"build": {}节点是专门为electron-builder服务的,在这里可以为各个平台配置目标的打包格式,比如我们希望Windows上是nsis,Mac上是dmg,等等。另外可以用一个“nsis”节点定制安装包的各种行为(更多配置选项参见官方文档):
"nsis": {"oneClick": false,"perMachine": true,"allowElevation": true,"allowToChangeInstallationDirectory": true,"uninstallDisplayName": "${productName}","deleteAppDataOnUninstall": true}
另外一个非常重要的节点是"publish"。有了这个节点,打包时才会生成自动升级必要的latest.yml文件:
"publish": [{"provider": "generic","url": "http://localhost:9000/test-bucket2"}],
注:正式部署时,上述url需要修改为线上环境的地址。
说白了,为了让electron-builder打包的程序能走通自动升级,服务端需要部署两个文件:新版本安装包.exe文件和latest.yml文件。(如果要支持增量升级,还需要同时部署.blockmap文件。老版本的.blockmap文件也要部署在线。)这些文件可以在打包输出目录、安装包exe的相同路径下找到。
演示程序准备好了,就可以开始测试了。还是使用“方案一”中搭建的Minio服务。这次在minio-data下创建一个新的子文件夹test-bucket2,并且将它的Access Policy改成Public。然后,执行如下步骤:
- package.json中“version”设置为初始版本,假定为1.0.0
- 执行npm run dist进行打包
- 运行out\xxx Setup 1.0.0.exe,将程序安装到本地硬盘
- 将package.json中“version”改为高版本,比如1.0.1
- 再次执行npm run dist进行打包
- 将out\xxx Setup 1.0.1.exe和latest.yml两个文件拷贝到D:\minio-home\minio-data\test-bucket2目录下
- 运行低版本v1.0.0演示程序,过几秒钟,不出意外的话就可以看到检测到新版本的弹窗啦!
GitHub完整演示代码:
https://github.com/luqiming666/AutoUpdater-nsis
补充几点说明:
- 客户端使用electron-updater 的autoUpdater来处理自动升级(而非Electron自带的autoUpdater),即const { autoUpdater } = require("electron-updater")
- 不需要调用autoUpdater.setFeedURL(),因为服务器地址已经在package.json的“publish”节点中指定了,在打包时会写入resources目录下的app-update.yml中,程序运行时会从这里读取配置。如果服务器URL有变更,修改package.json中的配置后重新打包即可。
- 如果想使用灰度发布(staged rollouts),可以在latest.yml里插入stagingPercentage字段,取值范围为0~100
- 在打包过程中没有找到可以插入releaseNotes的地方。可以在部署前修改latest.yml,手动插入这个字段:
两个方案介绍完了。最后再总结一下:
- 打包工具的选择。因为electron-forge打出来的包不能修改安装路径,所以选择electron-builder打出NSIS安装包,这也为安装程序的进一步定制留出足够空间。
- 在公有云上自建服务器,无须搭建第三方服务。对于NSIS形式的安装程序,只需要在公网URL可访问的服务器目录下放置两个文件:新版本安装包exe和与安装包同步打包出来的lastest.yml。
希望大家读了我这篇文章(以及文中链接的少量扩展阅读),就能把Electron客户端软件的自动升级跑通。不必经历我之前艰难的探索之路。如果还有问题,欢迎私信我。