
在最近的一个项目中用Electron包装了一个网站,生成了一个桌面应用。虽说渲染进程内容是被包装的网站,而网站的更新很灵活,但也不排除Electron主进程有时也会有需要更新的时候,因此,为Electron加上自动更新吧。
首先,我们要引入 autoUpdater
。electron里有一个 autoUpdater
但是这里我们使用electron-updater库里的autoUpdater。(PS:和原生autoUpdater的差别)
先是把 electron-updater 作用应用依赖去安装。
yarn add electron-updater
配置package.json中的publish设置
... "build": { "productName": "我的Electron应用", "copyright": "Copyright © 2019 Lunastudio.cn", "appId": "cn.lunastudio.myapp", "publish": { "provider": "generic",//这里我们使用普通更新提供器,我这里用的是腾讯COS "url": "https://upload-123456.cos.ap-chengdu.myqcloud.com/myApp/update/" //这里的地址就是cos的某个存放安装包的文件夹的目录地址,我们后边生成的安装包要扔到这上边来 }, "win": { "target": "nsis" }, "nsis": { "perMachine": true } }, ...
然后再去主程序中来写autoUpdater的相关逻辑
const {app, BrowserWindow, globalShortcut, session, dialog} = require('electron') const {autoUpdater} = require("electron-updater") let win; //保持单窗口模式。只有第一个窗口的应用才会有返回单例锁 const shouldQuit = app.requestSingleInstanceLock(); if (!shouldQuit) { //没有正常返回的非第一个窗口的应用程序,就直接退出,以免多开 app.quit() } function createWindow() { // 创建浏览器窗口。 win = new BrowserWindow({ webPreferences: { nodeIntegration: true } }) //使用electron-log,方便查看更新日志。 //日志被存放在 //on Linux: ~/.config/<app name>/log.log //on macOS: ~/Library/Logs/<app name>/log.log //on Windows: %USERPROFILE%\AppData\Roaming\<app name>\log.log const log = require("electron-log") log.transports.file.level = "debug" autoUpdater.logger = log autoUpdater.allowDowngrade = true; autoUpdater.autoInstallOnAppQuit = true; //窗口一建立,就开始检查是否需要更新 autoUpdater.checkForUpdatesAndNotify() win.loadURL('https://mysite.com/index.html') } app.on('ready', createWindow) //可以通过autoUpdater.on()这种事件监听的方式来处理,也可以直接用signals回调的方式,这里我用的是后者 autoUpdater.signals.progress(({total, delta, transferred, percent, bytesPerSecond}) => { //显示加载进度条 showProgressBar(percent); }) autoUpdater.signals.updateDownloaded(({version, releaseNotes, releaseName}) => { //在下载完后,显示一个对话框,提示安装 const dialogOpts = { type: 'info', buttons: ['开始安装'], title: '程序更新', message: `v${version}程序已经下载好了,点击“重启安装”` } dialog.showMessageBox(dialogOpts, (response) => { autoUpdater.quitAndInstall(true) }) }) //显示进度条 let progressBar, progressBarReady function showProgressBar(progress) { if (!progressBar) { progressBar = new ProgressBar({ //对于可以指明加载进度值的,要把这里的值设为false indeterminate: false, title: '正在更新新版本', text: '新版本下载中...', closeOnComplete: true, abortOnError: true, style: { text: { "overflow-y": 'hidden' } }, browserWindow: { modal: true, parent: null, resizable: false, closable: false, minimizable: false, maximizable: false, //注意这里的配置,如果不写出来,会导致进度条显示不出来 //@see https://github.com/AndersonMamede/electron-progressbar/issues/10 webPreferences: { nodeIntegration: true } } }); progressBar .on('ready', function () { progressBarReady = true; }) .on('completed', function () { console.info(`completed...`); progressBar.text = '下载完成,即将安装!'; progressBarReady = false; }).on('progress', function (value) { value = Math.round(value); progressBar.text = `新版本下载中...${value}%`; }); } //更新进度值 progressBarReady && (progressBar.value = progress); }
主程序差不多就是这个样子。package.json中写一个生成安装包的运行脚本
... "scripts": { "start": "electron index.js", "installer_build_win32": "electron-builder --win --ia32", } ...
当成运行npm run installer_build_win32的时候,就会在dist目录中生成对应版本的安装程序和blockmap文件,以及一个latest.yml文件。这三个文件我们都需要上传到上边提到的腾讯云COS文件夹中。当主程序在启动时执行app.requestSingleInstanceLock方法时,会向package.json中的publish里设定的目录中去找latest.yml文件。
如:https://upload-123456.cos.ap-chengdu.myqcloud.com/myApp/update/latest.yml
对比发现版本号有更新,就会自动开始下载新的安装文件。所以我们这里为了方便一点,需要在生成安装包后,立即把那三个新版本的相关文件上传上去。因为我使用的COS,所以就结合COS的SDK写了一个简单的脚本。
//uploader.js const COS = require('cos-nodejs-sdk-v5'); const md5File = require('md5-file/promise') const fs = require('fs'); const config = { SecretId: <COS_SECRET_ID>, SecretKey: <COS_SECRET_KEY>, Bucket: <COS_BUCKET>, Region: <COS_REGION>, ProgressInterval: 1000 }; async function uploadFile(file) { let cos = new COS(config); var fileStat = fs.statSync(file); var md5str = await md5File(file); if (uploadLog[md5str]) { return Promise.resolve(file); } uploadLog[md5str] = 1; //上传到myApp/update目录下 let key ='myApp/update/' + path.basename(file) return new Promise(resolve => { cos.putObject({ Bucket: config.Bucket, Region: config.Region, Method: 'PUT', Key: key, Body: fs.createReadStream(file), ContentLength: fileStat.size, onProgress: (progressData) => { console.log('uploading...', path.basename(file), Math.round(progressData.percent * 100) + '%') } }, function (err, data) { resolve(file); }); }) } function uploadFiles(files) { (files.map(uploadFile)).forEach(p => p.then(function (file) { console.log('uploaded file:', file); require("fs").writeFileSync('uploadLog.json', JSON.stringify(uploadLog), {encoding: 'utf-8'}) })) } const {version} = require('./package.json'); const uploadLog = require('./uploadLog.json') const path = require('path') let files = []; //新版本的三个关键文件 files.push(path.resolve(__dirname, './dist/latest.yml')) files.push(path.resolve(__dirname, `./dist/myApp Setup ${version}.exe`)) files.push(path.resolve(__dirname, `./dist/myApp Setup ${version}.exe.blockmap`)) //开始上传 uploadFiles(files);
最后就把“installer_build_win32”: “electron-builder –win –ia32”改成“installer_build_win32”: “electron-builder –win –ia32″ && node ./uploader.js”
这样就可以很方便地在打包完成就自动上传到COS服务器了。
最后,当程序启动时,发现有新版本时,就会提示

