Electron --- 关于自动更新的一系列折腾
Electron 自动更新的方法
- 使用 Electron 自己提供的 autoUpdater 模块
- 使用更新服务器
- 自己实现自动更新逻辑
为什么说经过了一系列的折腾呢, 因为前 2 中方式都没有解决我的问题,最后我是自己实现了自动更新的逻辑
没有解决我的问题是因为我需要兼顾到 mac 平台和 Windows 平台,然而 mac 平台比较麻烦,代码需要签名
我自己亲测方式一和方式二在 mac 平台上都需要代码签名, 而签名代码需要注册苹果开发者账号,需要付年费
于是这 2 条路就走不通了
最后我决定自己实现更新的逻辑
更新逻辑分析
- 自动触发或手动触发软件更新检查
- 服务器版本号大于本地版本才出现更新提示
- 对于更新,无非就是卸载之前的版本,安装新下载的安装包
- 软件的打包我选择的是 Electron Builder, 分别打成 dmg , setup.exe , app.zip
- 更新的时候先从服务器下载新版本
- 下载完成后对于安装包的安装分平台来说
Windows 下的更新
- Windows 下的安装包是 exe 可执行文件,安装包本身是有处理逻辑在里面的
- 于是我们只需要将安装包下载到临时目录,然后再软件里打开它,再退出软件,剩下的安装步骤交给用户
- 有一点需要注意的是,NSIS 的新安装包在安装前会自动卸载掉之前的版本,不过不会提示用户,我们可以在 nsis 脚本里加一个提示
MacOS 下的更新
- 相比于 Windows 下的安装包, macOS 下的 dmg 安装包就没有什么逻辑了,直接打开,然后将 app 文件拖到 Applications 目录中即可完成安装
- 于是有 2 中方法可选
- 一. 挂载 dmg, 找到挂载目录,在 mac 下是 /Volumes 目录下; 删除 /Applications 下的 app, 将 /Volumes 下的 app 拷贝到 /Applications 目录下; 再卸载 dmg; 重启应用即可,该方法可实现类似无缝更新的效果
- 二. 和方法一一个道理,只不过不是挂载 dmg 来查找 app, 直接解压 app.zip 压缩文件即可得到 app ,在使用相同的方式覆盖即可.
软件的版本控制
可以采取一个 json 文件来记录个版本的更新记录, 这里给个参考:
1 | [ |
代码参考
1 | import $ from 'jquery'; |
Electron --- Electron-Builder 打包的各种配置
打包的资源无法包含 build 目录
1 | "files": [ |
同时该配置也可防止源码被打包进去,
查看打包后的目录结构
"asar": false,
引入外部文件
1 | "extraResources": [ |
定义安装包输出目录
1 | "directories": { |
Windows 环境下打出 32 位和 64 位二合一包
1 | "win": { |
打出的 mac 包写入数据到 Info.plist 文件
1 | "mac": { |
NSIS 配置
1 | "nsis": { |
NSIS 脚本
1 | !macro customHeader |
NSIS 引入 license 文件包含中文的问题
当引入的 license 文件里有中文时, 在 Windows (中文操作系统) 平台下打包需要 GBK 编码, 在 macOS 下,GBK 编码会直接报错,需要修改为 UTF-8 编码
Electron --- Windows 和 MacOS 套娃图标的制作
什么是套娃图标
可能见过一种套娃的玩具,就是一个大的套着一个小的,每一个玩具的形状颜色都一样,只是大小比例不一样,套娃图标也是这个意思
什么工具
需要这么一个工具, IconFX : 下载
制做一组至少有 256256 (此外还有 128128 , 9696, 6464, 4848,3232,16*16)的一套图标, Windows 下格式为 icon, Mac 下格式为 icns
怎么制作
使用 PS 制作一张图片或者下载一张图片,按照下面的步骤完成所有大小的图标创建,之后保存.
图像 — 从图像创建 Windows 图标
MacOS 也是同样的道理
Electron --- 在 Windows 下和在 MacOS 下 Scheme 协议的使用
什么是 URL Scheme 协议
个人理解为注册一种协议来实现应用间的跳转
Windows 上的实现
Windows 上是通过注册表实现的
通过在 HKCR (HKEY_CALSSES_ROOT) 添加一条注册表记录
其中 command 的命令即为要执行的命令,注意后面要加一个参数 "%1"
Mac 上的实现
在应用里显示包内容,使用 xcode 查看 Info.plist 找到 URL types – URL Schemes 里添加一项
Electron 的实现
1 | app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, [`${__dirname}`]); |
这一句话即可完成 Windows 下和 macOS 下的协议注册,只不过需要应用启动后才可注册成功,就是说如果安装过后不打开的话,无法通过协议来唤醒应用,解决方式我们后面再讲
第一个参数为协议的名称, 第二个参数为执行的命令,第三个参数为所传字符串参数数组
在 Windows 环境下最后一项需要带上当前的项目路径,否则的话在开发模式下会打不开 electron 应用,打包完成后不会存在这个问题, mac 上也不会存在这个问题
Electron 上协议参数的处理
参数的处理分 2 中情况
- 新打开的窗口
- 打开的第二个实例
对于新打开的窗口:
使用 let argv = process.argv;
来获取进程参数,得到的是一个数组,如果做够一项包含我们的协议,则需要根据自己的字符串规则来进行处理
1 | let argv = process.argv; |
对于打开的第二个实例:
windows 上监听的事件是 second-instance
, mac 上监听的事件是 open-url
, 2 个事件传入参数还不一样, Windows 下传入的参数是字符串数组,mac 传入的参数是字符串,都包含了协议名称
1 | app.on('second-instance', (event, commandLine, workingDirectory) => { |
浏览器判断 scheme 协议是否存在
使用 setTimeout, 如果超时未打开的话则说明协议不存在
1 | let downloadURL = "http://xxxx"; |
Electron --- 知识点小记
Electron 只启动一个实例
使用 app.requestSingleInstanceLock()
1 | const gotTheLock = app.requestSingleInstanceLock(); |
Electron 不显示菜单栏
经过实测Menu.setApplicationMenu(null);
在 Windows 环境下没有菜单栏, 在 MAC 系统上开发模式下有菜单栏
正确的解决方式是Menu.setApplicationMenu(Menu.buildFromTemplate([]));
注册快捷键
electron 自带的注册快捷键的功能函数是 globalShortcut, 这个是全局的快捷键,就是说焦点不在当前程序上也能触发快捷键
我这里使用的是一个第三方的组件 electron-localshortcut
1 | electronLocalshortcut.register(win, 'F12', function () { |
主线程和渲染线程之间的通信
这里使用的是 ipcMain 和 ipcRenderer
渲染进程使用ipcRenderer.send发送异步消息,然后使用on事件监控主进程的返回值。主进程使用on事件监听消息,使用event.sender.send返回数据
App.js:
1 | const {ipcRenderer} = require('electron') |
main.js
1 | const {ipcMain} = require('electron') |
渲染进程使用ipcRenderer.sendSync发送同步消息。主进程使用on事件监控消息,使用event.returnValue返回数据给渲染进程。返回值在渲染进程中,就直接体现为ipcRenderer.sendSync的函数返回值
主线程如何给渲染线程发送消息
上面的示例没有说主线程如何对小渲染线程发送消息,应该这样做:
1 | win.webContents.send('ch-1', 'send'); |
渲染进程和渲染进程如何互发消息
- 渲染进程的页面自己处理
- 通过主线程进行中间转换
渲染线程如何使用 electron 的功能
渲染窗口添加配置:
1 | webPreferences: { |
添加 renderer.js
1 | global.electron = require('electron') |
渲染进程的页面使用:
1 | const electron = window.electron; |
主线程和渲染进程如何共享对象
不需要引入任何包,直接在主线程使用 global
1 | // 共享对象 |
渲染进程获取信息: let osInfo = electron.remote.getGlobal(‘shareObject’).osInfo;
主线程修改对象: global.shareObject.osInfo = message;
渲染线程修改对象: electron.remote.getGlobal(‘shareObject’).osInfo = null;
区分开发模式还是生产模式
建议使用 app.isPackaged
通过协议打开第二个实例的情况下触发的事件
Windows 环境下:
1 | app.on('second-instance', (event, commandLine, workingDirectory) => { |
Mac 环境下:
1 | // macOS |
开发环境和生成环境加载不同的页面
1 | if (app.isPackaged) { |
Create-React-App 的一些配置
添加多页面配置
npm run eject
- 修改 webpack.config.js
entry 修改:
这里我加了一个 update.html 页面
1 | entry: { |
output 修改
1 | output: { |
注意修改其中的 filename
HtmlWebpackPlugin 修改:
新增一个 HtmlWebpackPlugin
1 | new HtmlWebpackPlugin( |
在 public 目录里添加 update.html, 内容照抄 index.html 文件即可;
在 src 目录下添加 update.js 文件:
1 | import React from 'react'; |
之后, http://localhost:3000/update.html 即可访问; 如果想加个路径,直接修改 HtmlWebpackPlugin 里的 filename, 例如 filename: "index/update.html"
就可以 使用 http://localhost:3000/index/update.html 来访问
引入 src 目录以外的文件报错
例如需要引入 public 目录下的图片,就会报错,此时,注释掉
1 | // new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]), |
这一行,重启即可.
Electron --- Create-React-App + Antd + Electron 的搭建
步骤
创建 create-react-app-antd 项目
- git clone https://github.com/ant-design/create-react-app-antd
- npm install
- 将 webpack 所有内建的配置暴露出来,
npm run eject
, 如果发现错误,看下 package.json 里 eject 的脚本是不是为react-scripts eject
- 修改 config-overrides.js
1 | module.exports = function override(config, env) { |
修改 webpack.config.js 里的
module.rules.oneOf
支持 css 和 less, 添加1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36{
test: /\.(css|less)$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebookincubator/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
],
},
},
{
loader: require.resolve('less-loader'),
options: { javascriptEnabled: true }
},
],
}修改 start.js 注释掉下面代码关闭项目启动自动打开浏览器
1 | // openBrowser(urls.localUrlForBrowser); |
package.json 添加
"homepage": "."
,防止打包后的静态文件 index.html 引入 css 和 js 的路径错误App.less 修改为
@import '~antd/dist/antd.less';
添加 electron
- package.json 添加
"main": "main.js",
和 electron 依赖
1 | { |
- 创建 main.js,添加以下代码
1 | const {app, BrowserWindow, Menu} = require('electron'); |
- package.json 更改脚本
1 | { |
- 启动时先 react-start 再 eletron-start 即可看到效果