背景
我们使用 Electron 开发了一个桌面端开发工具 Ada 工作台,提速增效前端开发,在更新比较频繁的情况下,为了使整个更新体验更为顺畅、提升工作台的升级比率,需要优化当前的更新机制,尽量做到 VSCode 的无感知更新。
Electron 是一个使用 JavaScript, HTML 和 CSS 等 Web 技术创建原生应用的框架,使用Electron这个框架创建的桌面应用程序就是Electron应用。
Electron现有的相关类库:
打包工具:
- Electron Forge 除了打包还提供了项目脚手架、项目模版等功能
- electron-builder 打包、签名,完整的打包工具
更新服务器:
- Hazel 依赖 Github Release
- Nuts 依赖 Github Release
- electron-release-server 不需要依赖Github Release,单独部署
- Nucleus 不需要依赖Github Release,单独部署
现状
介绍了一些通用的背景知识之后,来看看我们现有的版本升级方案:
- 打包工具:通过electron-builder进行应用的打包和签名
- 分发服务器:打包后的安装程序上传到一个自建的服务器electron-release-server
- 升级逻辑:应用内自行编写更新逻辑,通过定时器查询是否有新的版本可供下载,如果有,将一个完整的安装程序下载到本地,下载完成后提示用户,用户确认后可以启动新的安装程序安装覆盖原有的版本
目前版本升级时的效果:
- Windows: 应用关闭,并出现一个小小的安装进度条用于等待应用安装完毕,安装完毕后自动启动新的应用
- macOS: 应用关闭,并出现安装界面,需要手动拖拽到应用文件夹,然后手动从应用文件夹内打开应用
看起来不错,已经实现了基本的更新逻辑,但是效果上让人不满意,与同样是Electron应用的VSCode的升级效果相差甚远,希望实现的效果是:让用户不用再等待一个新的安装过程,我们替用户去安装。每当有新版本发布时,用户只需要重新启动应用程序就能体验到最新的版本。
那么怎么才能实现这个效果呢?
调研
现有的打包工具和更新服务器已经在稳定运行,我们先看看它们能不能实现我们要的效果,如果不能,我们再去尝试别的类库。
我们使用的electron-builder和electron-release-server的文档中都有对auto update的描述。从这里可以看到服务器对更新文件是有类型要求的,比如OS X系统上,安装文件只支持dmg,更新文件只支持zip,Windows系统上,安装文件只能是exe,更新文件只能是完整的nupkg。而我们的打包工具是支持生成这些文件的(打包工具要求macOS应用要使用自动更新的话必须签名)。在应用代码中,则有electron-updater和官方的autoUpdater可供选择,用来检查是否需要更新,并处理具体的更新过程。
实施
在文档中看到macOS要实现自动更新的话,签名是必须的。
- macOS机器上打包:electron-builder会从系统的钥匙串里找到配置里或者环境变量CSC_LINK(CSC_NAME)的对应的证书来进行签名
- Windows机器上打包:一般有两种类型的证书,我们使用的是带USB加密器的EV Code Signing Certificate
签名之后也能让用户知道应用开发者的身份,不至于是来历不明的软件。
准备好签名证书之后,我们将现有的升级逻辑进行优化,这里直接使用了官方的autoUpdater:
- 使用autoUpdater中的方法替换现有方案中的升级逻辑
- 考虑自动升级失败的备用方案,在自动升级失败时,使用原有的升级逻辑中的方案,让用户重新安装完整的安装包
autoUpdater.on('checking-for-update', () => { // 开始检查是否有新版本 // 可以在这里提醒用户正在查找新版本 }) autoUpdater.on('update-available', (info) => { // 检查到有新版本 // 提醒用户已经找到了新版本 }) autoUpdater.on('update-not-available', (info) => { // 检查到无新版本 // 提醒用户当前版本已经是最新版,无需更新 }) autoUpdater.on('error', (err) => { // 自动升级遇到错误 // 执行原有升级逻辑 }) autoUpdater.on('update-downloaded', (ev, releaseNotes, releaseName) => { // 自动升级下载完成 // 可以询问用户是否重启应用更新,用户如果同意就可以执行 autoUpdater.quitAndInstall() })
为了配合electron-release-server对更新文件的要求,我们需要修改打包配置。
打包工具现有的打包配置mac关键词的target是default,也就是打包mac应用时默认会生成dmg和zip文件,而win关键词的target是默认的nsis,打包后只生成了exe文件,并没有nupkg文件。于是尝试将target改为squirrel,会按照server的要求生成nupkg文件用于更新,autoUpdater.quitAndInstall之后具体的效果是:
- Windows: 应用关闭,并出现一个可自定义的gif图片用于等待应用安装完毕,安装完毕后自动启动新的应用
- macOS: 应用关闭,大约一秒就启动了新的应用
对比我们现有的效果,在Windows上差别不大,但进度条相对于gif来说更好一点。在macOS上明显是新方案占优。于是我将Windows和macOS分别做了升级的逻辑,在Windows上依旧走旧的升级逻辑,在macOS上通过autoUpdater来升级,并用旧逻辑作为备用方案。到此为止,我们的更新方案就完成啦,最终的效果:
- Windows: 应用关闭,并出现一个小小的安装进度条用于等待应用安装完毕,安装完毕后自动启动新的应用
- macOS: 应用关闭,大约一秒就启动了新的应用
踩坑
大部分情况下我们不会是第一个遇到问题的人,无数前辈在互联网上留下了他们解决问题的方案,等着我们从搜索引擎中将他们找到,然而”定义问题“本身就不是一件容易的事。留下一些开发过程中遇到的问题的解决方案,当作后来者的宝藏。
autoUpdater.on('error', (err) => { console.log(err) //"Error: The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." })
第一个遇到的问题出现得猝不及防,错误的描述其实已经很清晰,App Transport Security 要求资源通过安全的连接来加载,也就是通过HTTPS协议。autoUpdater.setFeedURL的url地址一看是https的,然后就略过了这里,殊不知后来发现这个接口中我们又对electron-release-server的接口进行了封装,而electron-release-server的接口并不是https的。然后将appUrl改成https即可,btw,这个文档的位置可真难找。
electron-release-server更新接口的检查更新逻辑问题。
${ADA_AUTOUPDATE_FEED_API}${platform}/${version}/${channel}
channel为beta时,预期是不管任何时候,只要version不是最新的beta版本,接口应该返回最新的beta版本。但是实际发现这个接口的实现跟上传时间有关,比如version为2.18.0-beta.202008051710时,接口返回2.18.0-beta.202008142217。这是正常的。但是version为2.18.0-beta.202008051438时,接口返回2.18.0,返回的是stable的版本,并且没有按预期找到最新的beta版本。这是因为在接口内获取最新版本时,会根据版本创建时间去进行一次筛选,并且对channel采取了优先级的过滤,比如我们这里是beta版本,在获取新版本时会把优先级更高的rc,以及stable版本都包含进来。
没有考虑提交MR是因为看得出来作者在这里是有自己的考虑的,只是预期结果与我们希望的不一样而已,所以这里的解决方案是自行提供一个符合我们预期的接口。
- Windows上通过命令行打包乱码问题。
因为证书中有中文,所以需要修改命令行编码模式为utf-8,通过命令:
chcp 65001
,然后再次尝试即可。
总结
整体而言,Electron的开发体验还是很不错的,不管是官方文档还是第三方类库都比较完善,还有VSCode、Twitch、Facebook Messenger为它背书。桌面跨平台软件在可见的未来依旧是有不小的市场需求的,大家学起来吧!
招聘
我们是智联大前端,作为智联招聘的前端架构团队,我们在过去的几年中开创了细粒度的前端研发和发布模式,统一了移动端和桌面端的技术栈,搭建了灵活可靠的Serverless运行环境,率先落地了微前端方案,并且还在向FaaS和轻研发等方向不断迈进。
诚招前后端架构师和高级工程师,如果您也和我们一样热爱技术、热爱学习、热爱探索,就请加入我们吧!请将简历请发送至邮箱zpfe@group.zhaopin.com.cn,或者微信扫码了解详情。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。