Electron升級更新服務
目錄
一、 背景 2
二、 目標 2
三、 新插件的應用 3
(一) vue-cli-plugin-electron-builder是什么 3
(二) 打包服務electron-builder的配置 5
(三) 更新服務electron-updater 10
(四) 應用打包 18
(五) 測試更新 24一、 背景
很久前寫了一篇《Electron自動更新》介紹了當時存在的問題、自動更新的方案、打包的兩種方式和開發(fā)中存在的問題。當時文中應用采用的是 squirrel.windows 的更新機制和 nsis 的自定義安裝策略。通過 electron-builder 將兩者配置后,產(chǎn)出不同的安裝程序 setup.exe 和更新程序 nupkg。然后將 nsis 的 setup.exe 和 squirrel.windows 中的 nupkg 上傳到 electron-release-server 中。利用 electron-release-server 定時檢查策略,對比本地版本和線上版本,自動下載依賴和程序,進行更新并且替換,做到用戶無感知,操作不繁瑣。
二、 目標
《Electron自動更新》中的方案完成了當時的目標。
但是方案中的問題很多。
問題:
更新不受用戶控制,無法停止更新。沒法提示用戶是否更新。如果帶寬很小,應用下載完成后就更新,打斷了正在執(zhí)行的腳本; 更新方案在linux環(huán)境中不適用; electron-vue項目作者很久不維護; 官方提供的方案不詳細,自己編寫腳本偏多,考慮可能不全面。
現(xiàn)在的目標是:
更為完善的自動更新; 更新需要提示用戶,需要控制應用是否更新; 申請管理員權限; 更新時加入loading。 這時候,新插件 「vue-cli-plugin-electron-builder」 就出世了。
三、 新插件的應用
(一) vue-cli-plugin-electron-builder是什么
是一款構建帶有 electron 桌面應用的 vue.js 應用程序的插件。
它的 github 地址:https://github.com/nklayman/vue-cli-plugin-electron-builder。
它的文檔:https://nklayman.github.io/vue-cli-plugin-electron-builder/。
具體配置,大家可以去查看文檔,寫的很詳細。
優(yōu)點:
一次編寫,到處可用 可以定制 支持測試和調試
飛鴻覺得這個框架很友好,不需要開發(fā)者重寫更新服務,只需要管理好業(yè)務代碼并且提供靜態(tài)服務器用于更新,其他不需要開發(fā)者操心,一步到位。
創(chuàng)建步驟:
先用vue-cli3或4創(chuàng)建vue項目 在項目中添加electron-builder依賴 啟動應用:npm run electron:serve 打包應用:npm run electron:build
備注:當然這些命令你可以在package.json中修改。
vue create electron-builder-demo 創(chuàng)建項目,
在項目中添加插件 vue add electron-builder 開始下載 vue-cli-plugin-electron-builder

選擇 electron 版本,最好選擇最新的。

選擇后項目會去拉去electron,這個過程很漫長。如果等不了,看看之前有沒有electron項目,將依賴復制過來。
npm run electron:serve啟動應用:

那么這個命令他做了什么?
啟動內置開發(fā)服務器,并進行一些修改配合electron正常工作; 捆綁主進程; 啟動electron應用并告訴它加載上述開發(fā)服務器的 url。
最后自動打開應用界面:

你以為完了?不不不,還有很多步驟。
接下來我們要將打包配置和更新配置都要加上,才能將更新服務打通。
(二) 打包服務electron-builder的配置
此時還缺少打包配置,我們可以根據(jù) electron-builder文檔進行配置。之所以不使用electron-packager,是因為這個插件是基于electron-builder開發(fā)的。
其官網(wǎng)地址如下:https://www.electron.build/。
在根目錄下新增vue.config.js文件,填寫配置。
vue.config.js是一個可選的配置文件,如果@vue/cli-service存在于項目根目錄package.json中,項目會自動加載配置文件。
完整配置如下:
const path = require('path')
module.exports = {
// 部署應用包時的基本 URL
publicPath: '/',
// 生成的生產(chǎn)環(huán)境構建文件的目錄
outputDir: 'dist',
// 放置生成的靜態(tài)資源 (js、css、img、fonts) 的 (相對于 outputDir 的) 目錄
assetsDir: 'assets',
// 指定生成的 index.html 的輸出路徑 (相對于 outputDir)。也可以是一個絕對路徑。
indexPath: 'index.html',
// 文件名中包含hash
filenameHashing: true,
// 在 multi-page 模式下構建應用, 單頁面一般不需要考慮(詳情查看文檔配置)
pages: undefined,
// 保存時自動觸發(fā)eslint
lintOnSave: process.env.NODE_ENV !== 'production',
// 是否使用包含運行時編譯器的 Vue 構建版本
runtimeCompiler: false,
// babel 顯示轉譯一個依賴
transpileDependencies: ['socket.io-client'],
// 生產(chǎn)環(huán)境source map 關閉可提升打包速度
productionSourceMap: false,
// crossorigin: undefined,
// integrity: false,
css: {
// modules: false,
requireModuleExtension: true,
extract: process.env.NODE_ENV === 'production',
sourceMap: false,
loaderOptions: {
less: {
prependData: ``
}
}
},
// 并行打包
parallel: true, // 默認值require('os').cpus().length > 1,
pluginOptions: {},
// 本地開發(fā)服務器配置
devServer: {
// 自動打開瀏覽器
open: true,
// 設置為0.0.0.0則所有的地址均能訪問
host: '0.0.0.0',
port: 8888,
https: false,
hotOnly: false,
compress: true,
disableHostCheck: true,
// 使用代理
proxy: {
'/api': {
// 目標代理服務器地址
target: 'http://10.66.194.44:8081/',
// 允許跨域
changeOrigin: true,
},
},
},
// 針對webpack的配置,如果遇到上述配置,能使用的盡量不要改動webpack的配置
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
// 為生產(chǎn)環(huán)境修改配置...
config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true;
} else {
// 為開發(fā)環(huán)境修改配置...
}
},
// chain模式下的webpack plugin配置
chainWebpack: config => {
// 使用svg-sprite-loader的vue.config配置 只應用于src/icons目錄下
const svgRule = config.module.rule('svg')
svgRule.uses.clear()
svgRule
.test(/\.svg$/)
.include.add(path.resolve(__dirname, './src/icons')).end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
const fileRule = config.module.rule('file')
fileRule.uses.clear()
fileRule
.test(/\.svg$/)
.exclude.add(path.resolve(__dirname, './src/icons'))
.end()
.use('file-loader')
.loader('file-loader')
config
.plugin('env')
.use(require.resolve('webpack/lib/ProvidePlugin'), [{
jQuery: 'jquery',
$: 'jquery',
"windows.jQuery": "jquery"
}]);
config.resolve.alias.set('@', path.join(__dirname, './src'))
},
pluginOptions: {
electronBuilder: {
externals: ['log4js'],
// If you are using Yarn Workspaces, you may have multiple node_modules folders
// List them all here so that VCP Electron Builder can find them
nodeModulesPath: ['./node_modules'],
nodeIntegration: true,
chainWebpackMainProcess: (config) => {
// 修復HMR
config.resolve.symlinks(true);
config.resolve.alias.set('@', path.join(__dirname, './src'))
// Chain webpack config for electron main process only
},
chainWebpackRendererProcess: (config) => {
// 修復HMR
config.resolve.symlinks(true);
// 使用svg-sprite-loader的vue.config配置 只應用于src/icons目錄下
const svgRule = config.module.rule('svg')
svgRule.uses.clear()
svgRule
.test(/\.svg$/)
.include.add(path.resolve(__dirname, './src/icons')).end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
const fileRule = config.module.rule('file')
fileRule.uses.clear()
fileRule
.test(/\.svg$/)
.exclude.add(path.resolve(__dirname, './src/icons'))
.end()
.use('file-loader')
.loader('file-loader')
config.resolve.alias.set('@', path.join(__dirname, './src'))
// Chain webpack config for electron renderer process only (won't be applied to web builds)
},
// Changing the Output Directory
outputDir: "dist_electron",
// Electron's Junk Terminal Output https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/configuration.html#electron-s-junk-terminal-output
removeElectronJunk: false,
// Use this to change the entrypoint of your app's main process
mainProcessFile: 'background/main.js',
// Use this to change the entry point of your app's render process. default src/[main|index].[js|ts]
rendererProcessFile: 'src/main.js',
// Provide an array of files that, when changed, will recompile the main process and restart Electron
// Your main process file will be added by default
mainProcessWatch: ['background/main.js'],
// Provide a list of arguments that Electron will be launched with during "electron:serve",
// which can be accessed from the main process (src/background.js).
// Note that it is ignored when --debug flag is used with "electron:serve", as you must launch Electron yourself
// Command line args (excluding --debug, --dashboard, and --headless) are passed to Electron as well
// mainProcessArgs: ['--arg-name', 'arg-value']
builderOptions: {
"productName": "Vue Electron",
"appId": "com.VueElectron",
"publish": [{
"provider": "generic",
"url": "http://localhost:7777/dist_electron/"
}],
"win": {
"target": [
"nsis"
],
"icon": "./public/favicon.ico",
"requestedExecutionLevel": "highestAvailable"
},
"nsis": {
"oneClick": false,
"allowElevation": true,
"allowToChangeInstallationDirectory": true,
"installerIcon": "./public/favicon.ico",
"uninstallerIcon": "./public/favicon.ico",
"installerHeaderIcon": "./public/favicon.ico",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"perMachine": false,
"unicode": true,
"deleteAppDataOnUninstall": false
}
}
}
}
}
electron-builder 配置可以在 「pluginOptions」 配置,也可以通過外部配置導入的方式。
(三) 更新服務electron-updater
當然,要想要更新的話,需要加上更新插件。
之前飛鴻是使用 electron 自帶的 autoUpdater 插件,現(xiàn)在根據(jù)文檔建議,轉投 electron-updater 的 autoUpdater 懷抱了。
electron-updater 跟 electron 內置的 autoUpdater 還是有區(qū)別的:
只需要靜態(tài)服務器存放更新文件和版本文件,不需要專用的更新版本服務器,后者使用的就是electron-release-server服務器,優(yōu)缺點很明顯,這里我就不做比較了,可以參考飛鴻之前寫的《Electron自動更新》; 代碼簽名驗證不僅適用于macOS,也適用于Windows; 所有必須更新包都會自動生成并發(fā)布; 支持下載進度。
Windows 平臺是按照 nsis 更新,mac 是按照 DMG 更新,linux 是按照 AppImage 更新。
事件區(qū)別
| 事件 | 作用 | Electron自帶的autoUpdater | electron-updater |
|---|---|---|---|
| Checking-for-update | 開始檢查更新的時候觸發(fā) | 無法差異 | 無法差異 |
| Update-available | 又可以使用的更新的時候觸發(fā)更新。 | 自動下載,不可以阻斷 | 默認自動下載,可以阻斷轉化為手動。如果autoDownload為true,則會自動下載更新。提供 info |
| Update-not-available | 沒有可以使用的更新的時候觸發(fā) | 提供 info | |
| Update-downloaded | 更新下載完成的時候觸發(fā) | 更新包下載完成后可以阻斷自動更新。但是應用退出后會自動更新。 | 更新包下載完成后可以阻斷。如果autoInstallOnAppQuit為true,則應用退出后自動更新。為false,則應用退出后不會更新。 |
| Before-quit-for-update | 調用quitAndInstall()方法之后觸發(fā) | 無 | |
| Error | 當更新遇到錯誤的時候觸發(fā) | 提供error信息 | 提供error信息 |
| Download-progress | 無 | 顯示更新進度,提供progress 進度信息 bytesPerSecond percent total transferred |
方法區(qū)別
| 方法 | 作用 | Electron自帶的autoUpdater | electron-updater |
|---|---|---|---|
| autoUpdater.setFeedURL(url) | 設置檢查更新用的url,還初始化自動更新 | 提供url、headers、serverType(適用于mac) | 跟electron-builder配置的發(fā)布配置選項強相關,如provider、package、repo、owner等 |
| autoUpdater.getFeedURL() | 獲得當前更新的url地址 | ||
| autoUpdater.checkForUpdates() | 詢問服務器是否有更新 | 輪詢時間可以開發(fā)自己設置 | 輪詢時間可以開發(fā)自己設置 |
| autoUpdater.quitAndInstall() | 重啟應用并在下載后安裝更新。只能在update-download事件后被調用 | 調用這個方法將首先關閉應用,并在關閉后自動調用app.quit()。執(zhí)行一次自動更新可以不調用這個方法。但是在下一次打開應用的時候,應用檢測到更新包下載完成后會自動更新。 | 提供選項isSilent和isForceRunAfter isSilent默認false,windows平臺應用按照靜默模式安裝程序。isForceRunAfter即使是靜默安裝,也可以在完成后運行應用程序。 |
| autoUpdater.checkForUpdatesAndNotify() | 無 | 詢問服務器是否有更新,并且下載提示用戶 | |
| autoUpdater.downloadUpdate() | 無 | 如果autoDownload選項設置為false,就可以使用這個方法手動下載更新。 | |
| autoUpdater.channel() | 無 | 更換自動更新的channel,可以參考https://www.electron.build/tutorials/release-using-channels#release_using_channels |
?靜默模式安裝
??安裝時無需任何用戶干預,直接按默認設置安裝。就是更新的時候,應用不需要重新讓用戶選擇安裝路徑等操作,應用直接讀取之前的配置,按照第一次的安裝配置安裝,不顯示任務配置選項。
?
注意事項:
靜默模式安裝需要相同數(shù)量的臨時磁盤空間,并使用與標準安裝相同的臨時存儲目錄。如果臨時目錄中沒有足夠的空間,安裝程序不會提醒用戶。 靜默模式安裝需要與標準安裝相同的時間。在靜默模式安裝開始時,會短暫顯示初始安裝程序窗口或消息,指示安裝已啟動。沒有消息顯示,表明安裝正在進行或已成功完成。
類型配置選項
electron-updater獨有的類型配置選項
| 參數(shù) | 作用 | 備注 |
|---|---|---|
| autoDownload | 是否在找到更新時自動下載更新。默認為true | 這個需要和autoUpdater.downloadUpdate()搭配使用 |
| autoInstallOnAppQuit | 是否在應用退出時自動安裝下載的更新默認為true | 這個和autoUpdater.quitAndInstall()方法有關 |
| allowPrerelease 是 | 否允許更新到預發(fā)布版本默認為false | 這個和allowDowngrade有關。只支持github |
| fullChangelog | 獲取所有發(fā)行說明(從當前版本到最新版本),而不僅僅是最新版本。默認為false | 只支持github |
| allowDowngrade | 只支持github 默認為false | |
| channel | 獲取更新channel | 不支持github |
| requestHeaders | 請求頭 | |
| logger | 日志 | |
| signals | 為了類型安全,我們可以使用singals | |
| currentVersion | 當前應用程序版本信息 |
這樣看來,electron-updater基本上兼容了原生autoUpdater,而且提供的東西功能更多更有用。現(xiàn)在,飛鴻肯定是選擇electron-updater。相信大家肯定也是這樣的。
安裝使用
下載electron-updater依賴
npm install --save-dev electron-updater
使用依賴包
const {
autoUpdater
} = require("electron-updater");
Vue-cli-plugin-electron-builder 的更新,也是提供好幾個方案,如 github 這類的第三方托管平臺,參考更新例子:https://github.com/nklayman/electron-auto-update-example。
還有比如 minio 這類私有平臺,參考更新例子:https://github.com/iffy/electron-updater-example。
在electron-builder配置的publish中加入url參數(shù),指向minio指定的項目桶。當然我們的應用是可以區(qū)分環(huán)境的。
builderOptions: {
……
"publish": [{
"provider": "generic",
"url": process.env.VUE_APP_PUBLISHMINIO
}],
……
}
在主進程中,加入更新服務
……
autoUpdater.autoInstallOnAppQuit = false
autoUpdater.on('checking-for-update', () => {
logger.info('正在檢查更新……')
})
autoUpdater.on('update-available', (ev, info) => {
logger.info('下載更新包成功')
})
autoUpdater.on('update-not-available', (ev, info) => {
logger.info('現(xiàn)在使用的就是最新版本,不用更新')
})
autoUpdater.on('error', (ev, err) => {
logger.info('檢查更新出錯')
logger.info(ev)
logger.info(err)
})
autoUpdater.on('download-progress', (ev, progressObj) => {
logger.info('正在下載...')
})
autoUpdater.on('update-downloaded', (ev, releaseNotes, releaseName) => {
logger.info('下載完成,更新開始')
// Wait 5 seconds, then quit and install
// In your application, you don't need to wait 5 seconds.
// You could call autoUpdater.quitAndInstall(); immediately
const options = {
type: 'info',
buttons: ['確定', '取消'],
title: '應用更新',
message: process.platform === 'win32' ? releaseNotes : releaseName,
detail: '發(fā)現(xiàn)有新版本,是否更新?'
}
dialog.showMessageBox(options).then(returnVal => {
if (returnVal.response === 0) {
logger.info('開始更新')
setTimeout(() => {
autoUpdater.quitAndInstall()
}, 5000);
} else {
logger.info('取消更新')
return
}
})
});
……
開頭需要導入 autoUpdater 和 dialog 等方法
import {
autoUpdater
} from 'electron-updater'
import {
app,
protocol,
BrowserWindow,
dialog
} from 'electron'
最后需要加入更新檢測,什么時候加入檢測,看業(yè)務的需求,飛鴻在應用啟動的時候加入檢測。
app.on('ready', async () => {
createWindow()
autoUpdater.checkForUpdates()
})
在這里飛鴻將更新提示使用electron提供的dialog寫的,其實功能和checkForUpdatesAndNotify()一樣。
(四) 應用打包
打包生成兩個不同版本號的應用進行檢測更新。
執(zhí)行命令:
npm run electron:build
那它做了什么呢?
先用webpack打包渲染進程,打包出來的產(chǎn)物放在dist_electron,這個地址可以在配置中的outputDir中修改; 打包生成chunk-vendors.xxx.js、app.xxx.js和app.xxx.css放在其中的bundled\assets中; 渲染進程構建完成后,緊跟著構建主進程,捆綁后臺文件,還是打包進dist_electron/bundled下,生成background.js; 最后用electron-builder構建app,將web應用程序代碼構建成electron提供的桌面程序; 生成配置文件builder-effective-config.yaml; 最后用nsis構建應用。
electron-builder的配置:

builder-effective-config.yaml 就是 electron-builder 的配置項文件。

0.1.0版本打包成功:


雙擊打開應用



點擊完成,自動運行應用

又能看到應用界面了

桌面自動生成快捷方式

Windows開始菜單也會出現(xiàn)應用

(五) 測試更新
接下來就是打包一個高版本的應用來測試更新是否生效。
當前版本是0.1.0,我們來打包一個0.2.0的吧。
修改一下package.json中的version,最后重新打包。
打包0.2.0:

dist_electron目錄下就會出現(xiàn)兩個安裝包

應用是通過打包配置publish中的url提供的地址請求,檢測更新。
根據(jù)electron-builder的配置:

需要啟動一個端口為7777的靜態(tài)文件服務器。
python -m SimpleHTTPServer 7777
重啟0.1.0,打開就檢測到有新版本:

點擊取消,應用不更新,用戶可以進行這個版本的繼續(xù)操作。
查看日志

重啟0.1.0應用,再次出現(xiàn)應用提示界面,點擊確定,應用消失,緊接著回到應用安裝界面,當然可以選擇靜默安裝。

然后下一步安裝,最后啟動應用,就更新到了0.2.0。

再打開日志:

其中靜態(tài)服務器是用 python 起的,大家可以采用其他方式。
應用先拿取 latest.yml 文件,比較其中的哈希碼。
如果和本地不同,再去拉取最新的安裝包進行安裝。
應用和功能、桌面快捷方式、Windows開始菜單的應用從0.1.0都變成了0.2.0。

最后,希望大家一定要點贊三連。
可以閱讀我的其他文章,見blog地址
一個學習編程技術的公眾號。每周推送高質量的優(yōu)秀博文、開源項目、實用工具、面試技巧、編程學習資源等等。目標是做到個人技術與公眾號一起成長。歡迎大家關注,一起進步,走向全棧大佬的修煉之路
