研究Electron自動更新 系列一【近8k字】
摘要
閱讀本文需要 Electron 和 Node 的基礎(chǔ)知識,本文只針對 Electron 自動更新機(jī)制進(jìn)行研究。
目前文中的可行方案僅在 Windows 環(huán)境中適用,Mac 和 Linux 環(huán)境的方案亟待研究。
進(jìn)行了多個方案研究測試,方案簡述如下:利用內(nèi)置的 Squirrel 框架和 Electron 的 autoUpdater 模塊更新 Electron 應(yīng)用。
如果您對 Electron 還不是特別清楚,請見我的拙作[《Electron實戰(zhàn)》](https://github.com/qiufeihong2018/vuepress-blog/tree/master/docs/technical-summary/electron_exercise)一文。
關(guān)鍵詞
Electron Nsis Squirrel.windows Update.exe
正文
一、 背景

圖 1 Electron
在開發(fā) xxx 項目的時候,像很多做桌面應(yīng)用的框架(如包括較早的 vc6.0,以及 c#、asp.net、QT、java、Delphi、C++BUILDE 等等)一樣,Electron 需要打包出應(yīng)用程序,分發(fā)給每一個用戶使用。
二、 當(dāng)前存在的問題
當(dāng)我們在應(yīng)用中添加了新的功能并且提交了新代碼后,當(dāng)然是希望在用戶的電腦上能夠自動更新。但是最原始的操作是這樣的:
提交代碼后,用 Webpack打包混淆代碼,再用Electron打包工具打包壓縮代碼,生成可執(zhí)行文件;找到打包生成的 exe等執(zhí)行文件;將生成的 exe發(fā)給用戶,讓用戶重新安裝。
假如你是用戶,你肯定覺得很煩。不僅是用戶,連開發(fā)者都覺得很煩。有沒有一種方式可以派發(fā)更新,并在用戶的電腦上可以自動更新應(yīng)用呢?答案肯定是有的。
三、 自動更新的方案
先來看看,Electron 官網(wǎng)可以提供的幫助。Electron 團(tuán)隊保留 update.electronjs.org,一個免費的開源網(wǎng)絡(luò)服務(wù),Electron 應(yīng)用可以用來自我更新。這個服務(wù)是設(shè)計給那些滿足以下標(biāo)準(zhǔn)的 Electron 應(yīng)用:
應(yīng)用運(yùn)行在 macOS或者Windows;應(yīng)用有公開的 GitHub倉庫;編譯的版本發(fā)布在 GitHub Releases;編譯的版本已代碼簽名。
使用這個服務(wù)最簡單的方法是安裝 update-electron-app,一個預(yù)配置好的 Node.js 模塊來使用 update.electronjs.org。
但是 xxx 項目是公司私有項目,根據(jù)公司的網(wǎng)絡(luò)安全規(guī)則,不能將應(yīng)用部署于 GitHub,不能在 GitHub Releases 中公開發(fā)布,所以需要運(yùn)行自己的更新服務(wù)器。
沒關(guān)系,Electron 團(tuán)隊考慮到這個問題,給出了一些可以私有部署的更新服務(wù)器。方案中的更新服務(wù)器有以下這些:
Hazel——用于私人或開源應(yīng)用的更新服務(wù)器,可以在Now上免費部署。它從GitHub Releases中拉取更新文件,并且利用GitHub CDN的強(qiáng)大性能;Nuts——同樣使用GitHub Releases, 但得在磁盤上緩存應(yīng)用程序更新并支持私有存儲庫;Electron-release-server——提供一個用于處理發(fā)布的儀表板,并且不需要在GitHub上發(fā)布;Nucleus——一個由Atlassian維護(hù)的Electron應(yīng)用程序的完整更新服務(wù)器。支持多種應(yīng)用程序和渠道,使用靜態(tài)文件存儲來降低服務(wù)器成本。
官方的這些服務(wù)器經(jīng)過分析,Hazel 和 Nuts 依賴 GitHub releases 拉取更新文件,所以這兩個不適用。Electron-release-server 和 Nucleus 這兩種經(jīng)過測試是適用的。部署采用 Docker 技術(shù)。部署好后獲得的更新服務(wù)器的地址后面需要使用的。
部署好更新服務(wù)器后,就可以導(dǎo)入所需要的代碼模塊。下面的代碼只在打包的應(yīng)用程序,而不是開發(fā)中。
我是通過 app.isPackaged 屬性來區(qū)分開發(fā)和生產(chǎn)的:
if?(app.isPackaged)?{
??require('./update')
??getAutoUpdateDep()
}
導(dǎo)入依賴:
const?{?app,?autoUpdater,?dialog?}?=?require('electron')
下一步, 構(gòu)建更新服務(wù)器的 URL 并且通知 autoUpdater:
const?server?=?'服務(wù)器地址'
const?url?=?`${server}/update/${process.platform}/?${app.getVersion()}`
autoUpdater.setFeedURL({?url?})
作為最后一步,檢查更新。下面的示例將 1 小時檢查一次:
setInterval(()?=>?{
??autoUpdater.checkForUpdates()
},?3600000)
應(yīng)用程序被打包后, 它將接收我每次發(fā)布在服務(wù)器上的更新。
現(xiàn)在已經(jīng)為應(yīng)用程序配置了基本的更新機(jī)制,需要確保在更新時通知用戶。這可以使用autoUpdater API events 來實現(xiàn):
autoUpdater.on('update-downloaded',?(event,?releaseNotes,?releaseName)?=>?{
??const?dialogOpts?=?{
????type:?'消息',
????buttons:?['重啟',?'稍后'],
????title:?'應(yīng)用更新',
????message:?process.platform?===?'win32'???releaseNotes?:?releaseName,
????detail:?'應(yīng)用已經(jīng)更新了,請重啟'
??}
??dialog.showMessageBox(dialogOpts).then((returnValue)?=>
????if(returnValue.response?===?0)?autoUpdater.quitAnd()
??})
})
目前,xxx 項目更新的細(xì)致步驟,也就是每次執(zhí)行應(yīng)用程序時,UpdateManager 會執(zhí)行的步驟如下:
檢查更新。下載發(fā)行版位置的 RELEASES文件,并與本地RELEASES文件進(jìn)行比較,以檢查是否有更新;下載并驗證更新包。如果有一個新版本, UpdateManager決定是下載deltas還是最新的完整包(通過計算哪一個需要較少的下載)來更新到當(dāng)前版本。這些包與RELEASES文件中的SHA1進(jìn)行比較,以進(jìn)行驗證;從 Deltas構(gòu)建完整的包。如果已經(jīng)下載了delta包,那么將從以前的完整包和下載的delta文件創(chuàng)建一個新的完整包;安裝新版本。從完整包中提取當(dāng)前版本的 xxx項目,并基于版本號(例如app-1.0.1)將其放在新的%LocalAppData%\xxx項目安裝目錄中;快捷方式不更新。新 xxx項目安裝位置替換了原來的xxx項目位置,所以快捷方式地址還是舊的;前一個版本清理。安裝完成后,用 replace.bat將xxx項目新版本替換老版本。(例如更新到app-1.0.5后,app-1.0.4之前全部刪除)。目前,沒有內(nèi)置回滾到以前版本的支持。
有些步驟的具體代碼見第五節(jié)。
下面是兩種可行的更新服務(wù)器的解決方案以及他們所依賴打包更新機(jī)制。
(一) Electron-builder搭配Electron-release-server
對于 Electron-builder 的介紹,官網(wǎng)給得相當(dāng)詳細(xì),一個完整的解決方案,打包和建立準(zhǔn)備分發(fā) Electron 應(yīng)用程序的”auto update”支持開箱即用。下面是 Electron-builder 中 windows 的兩種配置方式,分別是 Squirrel.windows 和 Nsis,其他的就不提了。
Squirrel.windows自動更新,該方式默認(rèn)安裝到本地用戶帳戶下,安裝在%LocalAppData%下(例如,xxx項目就會被安裝在C:\Users\用戶\AppData\Local\xxx文件夾中),并且會自動產(chǎn)生packages文件夾和Update.exe程序。packages包含了版本信息的RELEASES文件。如果有最新版本,他的nupkg也會下載到其中。期間,自動更新的日志會存放在SquirrelSetup.log中。Nsis自動更新方式與Squirrel.windows類似。但是安裝后不會產(chǎn)生packages文件夾和Update.exe程序。打開后可以選擇安裝目錄等安裝步驟進(jìn)行安裝,安裝顆粒度變得更加細(xì)致。
當(dāng)創(chuàng)建項目時,腳手架可以選擇是否集成 Electron-builder,xxx 項目配置參數(shù)如下:
"build":?{
????"win":?{
??????"target":?[
????????"nsis",
????????"zip",
????????"squirrel"
??????],
??????"icon":?"xxx項目.ico",
??????"publish":?[
????????{
??????????"provider":?"generic",
??????????"url":?""
????????}
??????]
????},
????"squirrelWindows":?{
??????"iconUrl":?"https://path/to/valid/image.png"
????},
????"linux":?{
??????"icon":?"build/icons"
????},
????"nsis":?{
??????"oneClick":?false,
??????"allowElevation":?true,
??????"allowToChangeInstallationDirectory":?true,
??????"installerIcon":?"./xxx項目.ico",
??????"uninstallerIcon":?"./xxx項目.ico",
??????"installerHeaderIcon":?"./xxx項目.ico",
??????"createDesktopShortcut":?true,
??????"createStartMenuShortcut":?true,
??????"perMachine":?false,
??????"unicode":?true,
??????"deleteAppDataOnUninstall":?false
????},
????"extends":?null
??}
Electron-builder 打包生成的目錄如下:
build
├─icons
├─squirrel-Windows
├─xxx?Setup?0.0.1.exe
├─xxx?Setup?0.0.2.exe
├─xxx?Setup?0.0.3.exe
├─xxx-0.0.1-full.nupkg
├─xxx-0.0.2-full.nupkg
├─xxx-0.0.3-full.nupkg
└─win-unpacked?
????├─libs
...需要的依賴
????├─locales
????├─resources
????│??└─app.asar.unpacked
????│??????└─node_modules
...npm依賴
????└─swiftshader
├─xxx?Setup?0.0.1.exe
├─xxx?Setup?0.0.1.exe.blockmap
├─xxx?Setup?0.0.2.exe
├─xxx?Setup?0.0.2.exe.blockmap
├─xxx?Setup?0.0.3.exe
├─xxx?Setup?0.0.3.exe.blockmap
├─xxx-0.0.1-win.zip
├─xxx-0.0.2-win.zip
├─xxx-0.0.3-win.zip
build:打包產(chǎn)生的文件夾;Nupkg:是具有.nupkg擴(kuò)展的單個ZIP文件,此擴(kuò)展包含編譯代碼 (Dll)、與該代碼相關(guān)的其他文件以及描述性清單(包含包版本號等信息);squirrel-Windows:打包方式的產(chǎn)物,包括exe和nupkg。區(qū)別于nsis打包出來的exe;win-unpacked:源代碼。
詳細(xì)補(bǔ)充請見第四節(jié)的打包的兩種方式。
Electron-release-server 提供一個后臺 Squirrel.windows 自動更新。
Electron-release-server 將在以下接口提供 NuGet 包:
http://download.myapp.com/update/win32/:version/RELEASES http://download.myapp.com/update/win64/:version/RELEASES http://download.myapp.com/update/win32/:version/:channel/RELEASES http://download.myapp.com/update/win64/:version/:channel/RELEASES http://download.myapp.com/update/flavor/:flavor/win32/:version/RELEASES http://download.myapp.com/update/flavor/:flavor/win64/:version/RELEASES http://download.myapp.com/update/flavor/:flavor/win32/:version/:channel/RELEASES http://download.myapp.com/update/flavor/:flavor/win64/:version/:channel/RELEASES
如果未指定通道,然后 stable 將被用。如果 flavor 沒被指定, 那么 default 將被用。如果 win64 備用 但是只有 win32 資產(chǎn)可用,它將被使用。注意:如果需要,可以使用 windows_32 代替 win32 ,使用 windows_64 代替 win64。只要管理 Update.exe 或者 Squirrel.windows 去用 http://download.myapp.com/update/win32/:version/:channel 作為無需 query 的提要 URL。比如,xxx 項目只需要新建一個 version,上傳 .nupkg 和 setup.exe。
(二) Electron-forge搭配nucleus
Nucleus 這一款更新服務(wù)器和上述的服務(wù)器類似,electron-forge 打包跟 Electron-builder 類似,目前只知道它通過 squirrel 方式更新有效,但是它是否能使用 Nsis 安裝配置更新未知。
期間還調(diào)研了 Electron-packager、Electron-winstaller 和 Electron-updater 的打包自動更新方案,其打包原理大同小異。
前兩者的方式都是采用 Electron 原生的 autoupdate 方式,將 dist 打包成應(yīng)用程序的方式不同,packager 是一個命令行工具和 Node.js 庫,它將基于 Electron 應(yīng)用程序源代碼、重命名的 Electron 可執(zhí)行文件的和支持文件打包進(jìn)準(zhǔn)備發(fā)布的文件夾中;winstaller 是用于用 Squirrel 構(gòu)建 Electron 應(yīng)用程序成 Windows 安裝程序的 NPM 模塊 。最后一種是對原生 autoupdate 機(jī)制進(jìn)行封裝。
四、 打包的兩種方式
如果要談自動更新的話,必須要講講 Electron 的打包。
(一) Squirrel.windows方式

squirrel.window吉祥物圖 2 squirrel.window吉祥物
它是一組工具和一個庫,它可以完全管理安裝和更新用任何其他語言編寫的桌面 Windows 應(yīng)用程序。
Windows 應(yīng)用程序的安裝和更新應(yīng)該和谷歌 Chrome 一樣快、一樣容易。從應(yīng)用程序開發(fā)人員的角度來看,為應(yīng)用創(chuàng)建一個安裝程序并發(fā)布更新應(yīng)該是非常簡單的,而不需要經(jīng)歷一些繁瑣的步驟。正巧,Squirrel.windows 可以解決這一些繁瑣的步驟。
配置
為現(xiàn)有的 .NET應(yīng)用程序集成安裝程序應(yīng)該非常容易;客戶端 API應(yīng)該能夠檢查更新和接收一個(最好是HTML格式)更新日志;在安裝和更新期間,開發(fā)人員應(yīng)該控制自定義操作和事件; 卸載給了應(yīng)用程序一個清理的機(jī)會(例如,可以在卸載時運(yùn)行了一段代碼)。
打包
對于現(xiàn)有的應(yīng)用程序,生成安裝程序應(yīng)該非常簡單,就像對于 ClickOnce一樣;為我的應(yīng)用程序創(chuàng)建一個更新應(yīng)該是一個非常簡單的過程,很容易自動化; 打包將支持增量文件,以減少更新包的大小。
分發(fā)
托管一個更新服務(wù)器應(yīng)該是非常簡單的,并且應(yīng)該能夠使用簡單的 HTTP來完成(例如,xxx項目托管了electron-release-server);支持多種渠道發(fā)布。
安裝
安裝到本地用戶帳戶(例如,在 %LocalAppData%下);沒有重新啟動。
更新
更新應(yīng)該能夠在應(yīng)用程序運(yùn)行時應(yīng)用; 任何時候都不應(yīng)該強(qiáng)迫用戶停止他或她正在做的事情; 沒有重新啟動。

squirrel.window默認(rèn)安裝圖 3 Squirrel.windows 默認(rèn)安裝
(二) Nsis方式
全名:Nullsoft Scriptable Install System,是一個開源的 Windows 系統(tǒng)下安裝程序制作程序。它提供了安裝、卸載、系統(tǒng)設(shè)置、文件解壓縮等功能。這如其名字所指出的那樣,NSIS 是通過它的腳本語言來描述安裝程序的行為和邏輯的。NSIS 的腳本語言和通常的編程語言有類似的結(jié)構(gòu)和語法,但它是為安裝程序這類應(yīng)用所設(shè)計的。
nsis 鍵包含一組選項,指示 Electron-builder 如何構(gòu)建 nsis 目標(biāo)。這些選項也適用于 Web 安裝程序,使用頂級 nsisWeb 密鑰。
這個要詳細(xì)的講一下,這個 nsis 的配置指的是安裝過程的配置,其實還是很重要的,如果不配置 nsis 那么應(yīng)用程序就會自動的安裝在 C 盤。沒有用戶選擇的余地,這樣肯定是不行的。然后,我就想到一個好主意,要不讓 nsis 和 Squirrel.windows 的結(jié)合產(chǎn)生xxx 項目,既使用 Squirrel.windows 的更新機(jī)制,又使用 nsis 的自定義安裝。
關(guān)于 nsis 的配置是在 build 中 nsis 這個選項中進(jìn)行配置,下面是部分 nsis 配置:
"nsis":?{
??"oneClick":?false,?//?是否一鍵安裝
??"allowElevation":?true,?//?允許請求提升。?如果為false,則用戶必須使用提升的權(quán)限重新啟動安裝程序。
??"allowToChangeInstallationDirectory":?true,?//?允許修改安裝目錄
??"installerIcon":?"./build/icons/aaa.ico",//?安裝圖標(biāo)
??"uninstallerIcon":?"./build/icons/bbb.ico",//卸載圖標(biāo)
??"installerHeaderIcon":?"./build/icons/aaa.ico",?//?安裝時頭部圖標(biāo)
??"createDesktopShortcut":?true,?//?創(chuàng)建桌面圖標(biāo)
??"createStartMenuShortcut":?true,//?創(chuàng)建開始菜單圖標(biāo)
??"shortcutName":?"xxxx",?//?圖標(biāo)名稱
??"include":?"build/script/installer.nsh",?//?包含的自定義nsis腳本?這個對于構(gòu)建需求嚴(yán)格得安裝過程相當(dāng)有用。
}
關(guān)于 include 和 script 到底選擇哪一個?在對個性化安裝過程需求并不復(fù)雜,只是需要修改一下安裝位置,卸載提示等等的簡單操作建議使用 include 配置,如果你需要炫酷的安裝過程,建議使用 script 進(jìn)行完全自定義。xxx 項目上傳到服務(wù)器上的就是 nsis 打包出來的 setup.exe。

圖 4 nsis 自定義安裝
Nsis 和 Squirrel.windows 相同之處:
之前的應(yīng)用程序版本在更新后仍然存在。應(yīng)用程序的舊版本一直存在。 打包 - 打包應(yīng)用程序文件并準(zhǔn)備發(fā)布。 安裝 - 初始安裝應(yīng)用程序的過程。
不同之處:
Nsis有顆粒度更細(xì)致的安裝。Nsis無法生成packages文件夾和Update.exe程序。Squirrel.windows只能安裝在本地用戶帳戶中。Squirrel.windows集成 - 將Squirrel UpdateManager集成到您的應(yīng)用程序中。分發(fā) - 為用戶提供安裝和更新文件。更新 - 更新現(xiàn)有安裝的過程。
小結(jié)
系列一從自動更新的方案深入地講解了其中的原理,另外還講解了兩種打包方式。
后面的系列,讓我們來看看自動更新開發(fā)中出現(xiàn)的一些問題。

參考文獻(xiàn)
[更新應(yīng)用程序](https://www.electronjs.org/docs/tutorial/updates)
最后,希望大家一定要點贊三連。
可以閱讀我的其他文章,見[blog地址](https://github.com/qiufeihong2018/vuepress-blog)
一個學(xué)習(xí)編程技術(shù)的公眾號。每天推送高質(zhì)量的優(yōu)秀博文、開源項目、實用工具、面試技巧、編程學(xué)習(xí)資源等等。目標(biāo)是做到個人技術(shù)與公眾號一起成長。歡迎大家關(guān)注,一起進(jìn)步,走向全棧大佬的修煉之路
