Windows 下支持自动更新的 Electron 应用脚手架
前言
之前写过一篇关于 Windows 下通过 Electron 自带的 autoUpdater 实现应用自动更新的文章,很多小伙伴私信问我要具体实现代码,不过因为之前一直很忙(还有懒...)。
这两周正好周末比较空,除了在 github 上搭建了一个脚手架,还顺便把实现优化了一波,下面将会带着大家从零开始详细介绍实现过程,对小白也很友好的教程哦。
从零开始
进入你的工作目录,比如 d/workspace
# 目录 d/workspace
mkdir electron-demo # 新建文件夹 electron-demo
cd electron-demo # 进入文件夹 electron-demo
npm init # 初始化 npm,一路回车即可
npm i electron --save-dev # 安装 electron 依赖
touch main.js # 新建文件 main.js
touch index.html # 新建文件 index.html
现在你的文件结构如下:
|- electron-demo
|- main.js # 空文件
|- index.html # 空文件
|- package.json
|- package-lock.json # npm 自动生成的文件
|- node_modules
确保 package.json 的name,version,description这三个字段都不为空,main 字段的取值为 main.js 。
{
"name": "electron-demo",
"version": "0.0.1",
"description": "electron-demo",
"main": "main.js"
}
主进程和渲染进程
Electron 应用分为主进程和渲染进程。渲染进程是直接与用户产生交互的进程,也就是我们看到的一个个窗口页面,而主进程主要负责控制应用的生命周期,负责各个渲染进程的通信等。
我们的主进程代码写在 main.js 中,所以首先在你心爱的代码编辑中打开 main.js,输入如下代码:
const path = require('path');
const url = require('url');
const {
app,
BrowserWindow
} = require('electron');
app.on('ready', () => {
let win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
devTools: true
}
});
win.loadURL(
url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
})
);
});
app.on('window-all-closed', () => app.quit());
再打开 index.html,输入如下代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
之后执行
# 目录 d/workspace/electron-demo
node_modules/.bin/electron .
不出意外的话你会看到一个弹出框,像这样:
image.png
我们也可以把 node_modules/.bin/electron .
保存成 npm 脚本,方便以后调用。打开 package.json,添加如下内容:
"scripts": {
"start": "electron ."
}
以后只需要调用 npm start
即可。
到这里,我们已经有了一个最简单的 Electron 应用,如果你对继续开发应用本身更感兴趣的话,请移步 Electron 官方文档,因为接下来我们会专注在怎么让这个应用实现自动更新。
自动更新
安装依赖
自动更新功能的实现依赖 electron-builder 和 electron-updater,所以我们需要先安装这两个依赖。
# 目录 d/workspace/electron-demo
npm i electron-builder --save-dev # 必须安装为开发依赖,否则打包会出错
npm i electron-updater --save # 必须安装为运行依赖,否则运行会出错
配置 package.json
为了配合打包 package.json 需要新增字段 build:
"build": {
"appId": "com.xxx.app",
"publish": [
{
"provider": "generic",
"url": "http://127.0.0.1:8080"
}
]
},
同样为了执行方便,我们需要添加一个 npm 脚本,打开 package.json,添加如下内容:
"scripts": {
"start": "electron .",
"build": "electron-builder -w"
}
以后只需要调用 npm run build
即可。
主进程和渲染进程
打开main.js,编辑后内容如下:
const path = require('path');
const url = require('url');
const {
app,
BrowserWindow,
ipcMain
} = require('electron');
const { autoUpdater } = require('electron-updater');
const feedUrl = `http://127.0.0.1:8080/${process.platform}`;
let webContents;
let createWindow = () => {
let win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
devTools: true
}
});
webContents = win.webContents;
win.loadURL(
url.format({
pathname: path.join(__dirname, 'src/index.html'),
protocol: 'file:',
slashes: true
})
);
webContents.openDevTools();
};
let sendUpdateMessage = (message, data) => {
webContents.send('message', { message, data });
};
let checkForUpdates = () => {
autoUpdater.setFeedURL(feedUrl);
autoUpdater.on('error', function (message) {
sendUpdateMessage('error', message)
});
autoUpdater.on('checking-for-update', function (message) {
sendUpdateMessage('checking-for-update', message)
});
autoUpdater.on('update-available', function (message) {
sendUpdateMessage('update-available', message)
});
autoUpdater.on('update-not-available', function (message) {
sendUpdateMessage('update-not-available', message)
});
// 更新下载进度事件
autoUpdater.on('download-progress', function (progressObj) {
sendUpdateMessage('downloadProgress', progressObj)
})
autoUpdater.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
ipcMain.on('updateNow', (e, arg) => {
//some code here to handle event
autoUpdater.quitAndInstall();
})
sendUpdateMessage('isUpdateNow');
});
//执行自动更新检查
autoUpdater.checkForUpdates();
};
app.on('ready', () => {
createWindow();
setTimeout(checkForUpdates, 1000);
});
app.on('window-all-closed', () => app.quit());
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
<script>
const { ipcRenderer } = require('electron');
ipcRenderer.on('message', (event, { message, data }) => {
console.log(message, data);
switch (message) {
case 'isUpdateNow':
if (confirm('现在更新?')) {
ipcRenderer.send('updateNow');
}
break;
default:
document.querySelector('h1').innerHTML = message;
break;
}
});
</script>
</body>
</html>
打包
npm run build
第一次运行会比较慢,运行结束后会在当前目录下新增一个 dist 文件夹,dist 的目录结构如下:
|- dist
|- win-unpacked
|- electron-autoupdate-scaffold Setup.exe
|- electron-autoupdate-scaffold Setup.exe.blockmap
|- electron-builder-effective-config.yaml
|- latest.yml
win-unpacked 下是可执行文件,但是先别着急运行,我们还缺一个后台。
自动更新后台
聪明的你一定注意到,前面代码中我们有一个:
const feedUrl = `http://127.0.0.1:8080/${process.platform}`;
所以我们现在要做的就是一个可以接受这个请求的服务。
回到你的工作目录(d/workspace
)
# 目录 d/workspace
mkdir electron-server
cd electron-server
npm init
npm i koa --save
npm i koa-static --save
touch server.js
打开 server.js,输入如下内容:
// server.js
let Koa = require('koa');
let app = new Koa();
let path = require('path');
app.use(require('koa-static')(path.resolve(__dirname + '/packages')));
let server = app.listen(8080, () => {
let { address, port } = server.address();
console.log("应用实例,访问地址为 http://%s:%s", address, port);
});
将之前打包出来的 dist 目录下的 4 个文件(除了 win-unpacked)拷贝到这边的 packages/win32 下(新建目录 packages/win32),之后
# 目录 d/workspace/electron-server
npm start
到此为止,我们的自动更新服务就搭建完成了,现在来一波测试吧。
测试
-
进入 electron-demo/dist/win-unpacked 找到可执行文件,双击运行,看到打开窗口的控制台中依次输出:
checking-for-update update-not-available
-
进入 electron-demo,打开 package.json,把版本号改为0.0.2,重新打包后拷贝打包文件到自动更新后台目录(
d/workspace/electron-server/packages/win32
)。 -
进入 electron-demo,打开 package.json,把版本号改为0.0.1,重新打包后再次进入 dist/win-unpacked 目录,运行 exe,看到打开窗口的控制台中依次输出:
checking-for-update update-available
并且出现弹窗提示「现在更新?」。