大家好,我是多喝热水。
在踩了数不清的坑之后,终于从 0 到 1 完成了一个桌面端应用,但万万没想到,最最痛苦的还不是开发过程,而是开发完成后的打包签名阶段,这真是踩坑踩麻了!!!
ok,踩坑归踩坑,收获也是不小的,所以这篇文章会介绍一个 Electron 应用如何从 0 到 1 完成打包 & 签名 & 自动更新 等一系列流程,全程干货,建议点赞收藏!
写在前面
文章中所使用的环境如下,读者可作参考:
1)nodejs 版本:v18.19
2)electron 版本:v27.1.0
3)electron-builder 版本:v24.6.4
4)electron-rebuild 版本:v3.2.9
5)electron-notarize 版本:v1.2.2
6)electron-updater 版本:v6.1.4
7)node-abi 版本:v3.52.0
大家也可以根据自己安装的 Electron 版本来安装对应的 Node 版本,两者必须对应上,否则打包必出错,参考官方文档。
一、打包
打包有一个非常重要的前提,那就是安装包和系统对应,比如我想打包MacOS的安装包,那么我就需要使用 MacOS
电脑,Windows
同理,如果你没有,那你可以考虑安装一个虚拟机来打包。
MacOS 平台打包
1)准备好electron-builder配置文件,如下,具体的参数含义可参考官方文档
js
const path = require('path');
const fs = require('fs');
module.exports = async function () {
function findFilesInDirectory(directoryPath, result = []) {
const files = fs.readdirSync(directoryPath);
files.forEach(file => {
const filePath = path.join(directoryPath, file);
const stats = fs.statSync(filePath);
if (stats.isFile()) {
result.push({from: filePath, to: '.'}); // 将主进程中用到的静态资源输出到打包后的软件根目录
} else if (stats.isDirectory()) {
result = findFilesInDirectory(filePath, result);
}
});
return result;
}
const staticResources = findFilesInDirectory(path.join('packages', 'main', 'resources'));
return {
asar: true, // 代码加密
productName: '【软件名称】',
compression: 'maximum', // 压缩代码
directories: {
output: `dist`, // 将打包产物输出到dist文件
},
files: ['packages/**/dist/**'],
extraResources: [...staticResources],
mac: {
target: ['dmg'],
icon: 'buildResources/icon.icns' // 软件图标
},
dmg: {
icon: 'buildResources/icon.icns',
iconSize: 100,
}
};
};
2)应用中是否用到了 C / C++ 编写的依赖包?如果没有那么可忽略此步骤,如果有,那么需要使用 electron-rebuild
对 node_modules
预构建一次,且构建会需要用到 node-gyp
(确保是最新版本的 )+ Python
环境(Mac 自带的即可),运行命令如下:
bash
electron-rebuild
3)配置 package.json 中的运行脚本
js
"scripts": {
"build": "npm run build:main && npm run build:preload && npm run build:renderer",
"build:main": "cd ./packages/main && vite build", // 主进程代码打包
"build:preload": "cd ./packages/preload && vite build", // 预渲染进程代码打包
"build:renderer": "cd ./packages/renderer && vite build", // 渲染进程代码打包
"compile": "rm -rf ./dist && npm cache clean --force && cross-env MODE=production npm run build && electron-builder build --config 【上面编写的配置文件名称】",
},
4)开始打包
bash
npm run compile
打包完成之后我们就会看到生成一个 dist 文件夹,里面包含了一个 dmg 安装包和一些 yml 文件,此时我们就完成了第一步,如果你失败了,那么大概率是网络问题,可以重新打包,必要时清理一下 npm 缓存。
Windows 平台打包
1)windows 打包命令和 MacOS 一致,不同的是 electron-builder 的配置,我们对 electron-builder 配置文件进行改造,如下:
js
const path = require('path');
const fs = require('fs');
module.exports = async function () {
function findFilesInDirectory(directoryPath, result = []) {
const files = fs.readdirSync(directoryPath);
files.forEach(file => {
const filePath = path.join(directoryPath, file);
const stats = fs.statSync(filePath);
if (stats.isFile()) {
result.push({from: filePath, to: '.'}); // 将主进程中用到的静态资源输出到打包后的软件根目录
} else if (stats.isDirectory()) {
result = findFilesInDirectory(filePath, result);
}
});
return result;
}
const staticResources = findFilesInDirectory(path.join('packages', 'main', 'resources'));
return {
asar: true, // 代码加密
productName: '【软件名称】',
compression: 'maximum', // 压缩代码
directories: {
output: `dist`, // 将打包产物输出到dist文件
},
files: ['packages/**/dist/**'],
extraResources: [...staticResources],
nsis: {
oneClick: false,
perMachine: false,
allowToChangeInstallationDirectory: true,
deleteAppDataOnUninstall: false,
},
win: {
icon: 'buildResources/icon.ico', // 软件图标
target: [
{
target: 'nsis', // 使用 NSIS 进行打包
arch: ['x64'],
},
],
},
};
};
2)接下来的步骤与MacOS一致,这里不过多赘述,如下:
bash
electron-rebuild
3)开始打包
bash
npm run build
打包完成你可以在dist文件夹中看到如下文件和安装包:
此时我们已经完成了第一步,接下来我们需要对软件进行代码签名。
二、代码签名
代码签名需要哪些前置条件?
Windows 平台:购买EV证书,它可以立刻消除警告,但是这个证书非常昂贵,基本上都是 2000+的价格,且有效期只有一年
MacOS 平台:加入苹果开发者,价格是 99 美元,约为人民币 600+,同样有效期为一年
所以我们光签名就已经花费了 3000 左右,可能有些人在这里就已经劝退了,那不签名行不行?
不做代码签名会遇到哪些限制?
试想一下,你有一包辣条,但是你不知道这包辣条是哪个厂家生产的,你大概率会选择不吃!但如果辣条包装上有生产厂家和生产日期等信息,你吃起来是不是就更放心了?软件签名的道理也是如此!
1)在浏览器下载完这个软件的时候你可能会看到以下警告(图片出自网络),这样一个提示肯定是不利于传播的,如果是我,我可能会选择删除。。
2)即使下载后打开应用,你依然可能遇到如下问题(大致意思就是建议用户不要打开该软件,因为它是未知的开发者发布的)
3)在 MacOS 提示更为严重的软件损坏(直接就不让打开了,我辛辛苦苦开发的软件不让人用这还搞个毛线啊),如下:
MacOS 代码签名 & 公证
苹果签名的大致步骤拆分如下,更详细步骤移步 Electron 官网 代码签名 | Electron
1)加入 苹果开发者 (需要缴纳年费)
2)生成 & 下载签名证书,然后安装到本机, 苹果签名证书下载地址
3)打包签名后进行公证(不公证仍然会提示软件已损坏)
1. 加入苹果开发者
1)点击苹果开发者注册地址,进入登录自己的 AppleID,然后就会看到如下界面:
2)点击注册,会让下载一个软件 Apple Developer,如下图:
3)打开Apple Developer 去注册苹果开发者,如下:
4)点击立即注册,如下:
5)按要求填写信息,如下:
这里需要注意,如果是以公司的名义去注册的,那么你还需要注册一个邓白氏编码(D-U-N-S),在后续会用到。
2. 生成签名证书
1)注册完成后,我们打开本机电脑的钥匙串访问 => 证书助理 => 从证书颁发机构请求证书,如下:
2)填写邮箱,并选择存储到磁盘,如下:
3)点击继续之后就会创建一个证书,提示存储到哪里,我们先存储到桌面上
4)前往苹果开发者官网申请 Developer ID Application(需要用到我们刚才请求的证书,此步骤需要账户持有人操作)
点击 Choose File,上传我们之前在自己电脑上申请的证书文件,上传完成后我们就可以得到Developer ID Application 证书,将它下载保存到桌面。
3. 安装签名证书
1)将下载到桌面的证书拖拽到钥匙串访问的证书列表中,并将 私钥 导出为 .p12 文件
2)打开终端配置环境变量,此处参考的是 electron-builder 官方文档
3)输入如下两个环境变量,对应的是 p12 文件的地址和证书对应的密码,配置完成后使用 source 命令让刚才配置的环境变量生效
4)输入 env 查询是否配置成功,如果显示了我们填写的环境变量地址,那就成功了
4. 打包 & 签名
在做完以上操作后,目前你不需要对 electron-builder 配置做任何更改,直接可以运行打包命令,在打包过程中你会看到如下 signing 正在签名的提示,如下:
验证是否签名,你可以在软件包内容中你可以看到 _CodeSignature
的文件夹,表示该应用已经被成功签名了,如下:
5. 公证
苹果平台除了签名,还需要进行公证,两者缺一不可,公证我们需要用到 electron-notarize 这个包,具体的配置如下:
1)appbundleId:在苹果开发者中注册的应用 ID
2)appPath:固定写法,照抄即可
3)appleId:苹果开发者的苹果 ID
4)appleIdPassword:临时密码生成地址,临时密码需要保存好,因为关掉后它不会再显示了
5)ascProvider:团队 ID 查看地址
6)tool:签名工具,公证工具
7)teamId:团队 ID 同 ascProvider
buildResources/notarization/notarize.js
js
const {notarize} = require('electron-notarize');
exports.default = async function notarizing(context) {
const {electronPlatformName, appOutDir} = context;
if (electronPlatformName !== 'darwin') {
return;
}
const appName = context.packager.appInfo.productFilename;
// console.log(`打包后应用地址:${appOutDir}/${appName}.app`);
return await notarize({
appBundleId: 'com.xxx.xxx',
appPath: `${appOutDir}/${appName}.app`, //打包后的放置app文件的命名和路径【固定写法】
appleId: 'xxx@qq.com',
appleIdPassword: 'xxxx', // 临时密码
ascProvider: '团队ID',
tool: 'notarytool', // 公证工具 固定写法
teamId: '团队ID',
});
};
.electron-builder.config.js
在 afterSign 选项中加入我们公证逻辑的地址,该字段表示打包完成之后进行公证
加完这些配置之后再次重新打包即可完成公证!
Windows 代码签名
windows 签名也比较简单,但是在 Windows 上花费的时间不比苹果少,这里建议大家不要去开微软开发者,如果你不打算把软件分发到微软商店的话,那开通微软开发者对签名没有什么帮助,这也是我们花费了 99 刀踩过的坑!
1. 购买签名证书
现在的签名证书都是以 U 盾的形式发放(快递邮寄,一般都是国外发货),没有数字签名了,这里我调研了很多平台,确信!建议从淘宝代理那购买 Sectigo EV 代码签名证书,比较便宜。
2. 安装签名证书
1)安装 SafeNet Authentication Client
软件,这个软件是用来安装证书的,安装地址。
2)插入包含签名证书的 U盾,然后我们就会在软件左侧看到对应的证书信息
3)安装签名证书
选择安装到当前用户或本机都可以,最终我们选择安装到受信任的根证书颁发机构
4)导出签名证书 cer 文件到桌面
我们使用 Win + R 唤出如下界面,并输入 certmgr.msc
找到我们的证书所在位置
右键导出 cer 文件到桌面
5)打开签名软件的设置页,设置单点登录(因为等会打包会涉及到多次密码输入,为了简化流程)
6)初始密码太繁琐了,我们可以修改一下证书的初始密码( Token Password ),这个证书的初始密码可以在你购买时填写的邮箱中找到
7)找到我们导出的证书文件,现在是 cer 格式的,我们需要将它 重命名为 pfx 格式,因为 electron 打包只支持 pfx 格式的文件
3. 打包 & 签名
将 certificate.pfx
文件拖入项目根目录,在electron-builder中加入如下配置:
js
return {
appId: 'com.xxx.xxx',
nsis: {
oneClick: false,
perMachine: false,
allowToChangeInstallationDirectory: true,
deleteAppDataOnUninstall: false,
},
win: {
icon: 'buildResources/icon.ico',
verifyUpdateCodeSignature: true, // 更新校验签名
signingHashAlgorithms: ['sha256'],
signAndEditExecutable: true,
signDlls: false,
publisherName: '上海xxxx有限公司',
certificateSubjectName: '',
rfc3161TimeStampServer: 'http://timestamp.sectigo.com',
target: [
{
target: 'nsis', // 使用 NSIS 进行打包
arch: ['x64'],
},
],
certificateFile: 'certificate.pfx', // 我们导出的证书
certificatePassword: 'xxxxxx', // 上面我们修改的密码
},
mac: {
// ...
},
dmg: {
// ...
}
};
};
现在可以执行打包命令,打包过程中你会看到 signing 正在签名的提示,打包结束为了验证是否完成签名,你可以右键打包出来的exe产物,查看数字签名栏的信息,如果显示的是你填写的 publisherName
字段值,那么表示签名成功,如下:
三、自动更新
为什么要做自动更新?
用户下载了你的软件,如果每次都需要去官网下载更新的话,那么久而久之用户必然会觉得很烦,甚至产生卸载软件的念头,所以我们需要做的就是将用户手动去下载更新这个操作自动化,换成自动下载安装包完成更新。
如何实现自动更新?
1)我们需要用到 electron-updater
这个包,主要需要用到 autoUpdater
这个对象
js
import { type BrowserWindow } from 'electron';
import { autoUpdater } from 'electron-updater';
import { createModalWindow } from './windows/modal';
/**
* 自动更新检测
*/
export const checkUpdate = () => {
// 创建设置窗口
function updateAvailable() {
// 创建有新版本的弹窗提示
createModalWindow();
}
//
function updateDownloaded() {
// 安装包下载完成,将自动安装,给出提示...
// ...
}
// 开启自动安装 & 自动下载
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.autoDownload = true;
if (process.platform === 'darwin') {
// 苹果平台【interl/M系列区分】
const ARM = process.arch == 'arm64';
const feedUUL = ARM
? 'https://xxxx.com/installer/mac/arm'
: 'https://xxxx.com/installer/mac/intel';
autoUpdater.setFeedURL(feedUUL);
} else {
// windows平台
autoUpdater.setFeedURL('https://xxxx.com/installer/win');
}
// 检测新版本
autoUpdater.checkForUpdatesAndNotify().catch();
// 发现可用的安装包
autoUpdater.once('update-available', updateAvailable);
// 安装包下载完成了
autoUpdater.once('update-downloaded', updateDownloaded);
};
2)在 .electron-builder.config.js
中加入 publish 字段,用于检测当前地址中latest.yml文件记录的版本
在加入了publish字段后我们再打包会生成一个latest.yml文件(苹果是latest-mac.yml),该文件我们需要上传到自己的安装包所在的目录,检测新版本就是检测 latest.yml文件中记录的版本
latest.yml 文件格式
完结
好了,到这里整个流程就算是跑完了,大家有不懂的可以在评论区讨论,文章哪里有写的不好的欢迎大佬指点,我太想进步了!!🥹