大家好,我是多喝热水。

在踩了数不清的坑之后,终于从 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配置文件,如下,具体的参数含义可参考官方文档

 1const path = require('path');
 2const fs = require('fs');
 3
 4module.exports = async function () {
 5  function findFilesInDirectory(directoryPath, result = []) {
 6    const files = fs.readdirSync(directoryPath);
 7    files.forEach(file => {
 8      const filePath = path.join(directoryPath, file);
 9      const stats = fs.statSync(filePath);
10      if (stats.isFile()) {
11        result.push({from: filePath, to: '.'}); 
12      } else if (stats.isDirectory()) {
13        result = findFilesInDirectory(filePath, result);
14      }
15    });
16    return result;
17  }
18  const staticResources = findFilesInDirectory(path.join('packages', 'main', 'resources'));
19  return {
20    asar: true, 
21    productName: '【软件名称】',
22    compression: 'maximum', 
23    directories: {
24      output: `dist`, 
25    },
26    files: ['packages/**/dist/**'],
27    extraResources: [...staticResources],
28    mac: {
29      target: ['dmg'],
30      icon: 'buildResources/icon.icns' 
31    },
32    dmg: {
33      icon: 'buildResources/icon.icns',
34      iconSize: 100,
35    }
36  };
37};

2)应用中是否用到了 C / C++ 编写的依赖包?如果没有那么可忽略此步骤,如果有,那么需要使用 electron-rebuildnode_modules 预构建一次,且构建会需要用到 node-gyp确保是最新版本的)+ Python 环境(Mac 自带的即可),运行命令如下:

 1electron-rebuild

3)配置 package.json 中的运行脚本

 1"scripts": {
 2    "build": "npm run build:main && npm run build:preload && npm run build:renderer",
 3    "build:main": "cd ./packages/main && vite build", 
 4    "build:preload": "cd ./packages/preload && vite build", 
 5    "build:renderer": "cd ./packages/renderer && vite build", 
 6    "compile": "rm -rf ./dist && npm cache clean --force && cross-env MODE=production npm run build && electron-builder build --config 【上面编写的配置文件名称】",
 7  },

4)开始打包

 1npm run compile

打包完成之后我们就会看到生成一个 dist 文件夹,里面包含了一个 dmg 安装包和一些 yml 文件,此时我们就完成了第一步,如果你失败了,那么大概率是网络问题,可以重新打包,必要时清理一下 npm 缓存。

Windows 平台打包

1)windows 打包命令和 MacOS 一致,不同的是 electron-builder 的配置,我们对 electron-builder 配置文件进行改造,如下:

 1const path = require('path');
 2const fs = require('fs');
 3
 4module.exports = async function () {
 5  function findFilesInDirectory(directoryPath, result = []) {
 6    const files = fs.readdirSync(directoryPath);
 7    files.forEach(file => {
 8      const filePath = path.join(directoryPath, file);
 9      const stats = fs.statSync(filePath);
10      if (stats.isFile()) {
11        result.push({from: filePath, to: '.'}); 
12      } else if (stats.isDirectory()) {
13        result = findFilesInDirectory(filePath, result);
14      }
15    });
16    return result;
17  }
18  const staticResources = findFilesInDirectory(path.join('packages', 'main', 'resources'));
19  return {
20    asar: true, 
21    productName: '【软件名称】',
22    compression: 'maximum', 
23    directories: {
24      output: `dist`, 
25    },
26    files: ['packages/**/dist/**'],
27    extraResources: [...staticResources],
28    nsis: {
29      oneClick: false,
30      perMachine: false,
31      allowToChangeInstallationDirectory: true,
32      deleteAppDataOnUninstall: false,
33    },
34    win: {
35      icon: 'buildResources/icon.ico', 
36      target: [
37        {
38          target: 'nsis', 
39          arch: ['x64'],
40        },
41      ],
42    },
43  };
44};

2)接下来的步骤与MacOS一致,这里不过多赘述,如下:

 1electron-rebuild

3)开始打包

 1npm 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

 1const {notarize} = require('electron-notarize');
 2
 3exports.default = async function notarizing(context) {
 4  const {electronPlatformName, appOutDir} = context;
 5  if (electronPlatformName !== 'darwin') {
 6    return;
 7  }
 8  const appName = context.packager.appInfo.productFilename;
 9  
10  return await notarize({
11    appBundleId: 'com.xxx.xxx',
12    appPath: `${appOutDir}/${appName}.app`, 
13    appleId: 'xxx@qq.com',
14    appleIdPassword: 'xxxx', 
15    ascProvider: '团队ID',
16    tool: 'notarytool', 
17    teamId: '团队ID',
18  });
19};

.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中加入如下配置:

 1return {
 2    appId: 'com.xxx.xxx',
 3    nsis: {
 4      oneClick: false,
 5      perMachine: false,
 6      allowToChangeInstallationDirectory: true,
 7      deleteAppDataOnUninstall: false,
 8    },
 9    win: {
10      icon: 'buildResources/icon.ico',
11      verifyUpdateCodeSignature: true, 
12      signingHashAlgorithms: ['sha256'],
13      signAndEditExecutable: true,
14      signDlls: false,
15      publisherName: '上海xxxx有限公司',
16      certificateSubjectName: '',
17      rfc3161TimeStampServer: 'http://timestamp.sectigo.com',
18      target: [
19        {
20          target: 'nsis', 
21          arch: ['x64'],
22        },
23      ],
24      certificateFile: 'certificate.pfx', 
25      certificatePassword: 'xxxxxx', 
26    },
27    mac: {
28    
29    },
30    dmg: {
31    
32    }
33  };
34};

现在可以执行打包命令,打包过程中你会看到 signing 正在签名的提示,打包结束为了验证是否完成签名,你可以右键打包出来的exe产物,查看数字签名栏的信息,如果显示的是你填写的 publisherName 字段值,那么表示签名成功,如下:

为什么要做自动更新?

用户下载了你的软件,如果每次都需要去官网下载更新的话,那么久而久之用户必然会觉得很烦,甚至产生卸载软件的念头,所以我们需要做的就是将用户手动去下载更新这个操作自动化,换成自动下载安装包完成更新。

如何实现自动更新?

1)我们需要用到 electron-updater 这个包,主要需要用到 autoUpdater 这个对象

 1import { type BrowserWindow } from 'electron';
 2import { autoUpdater } from 'electron-updater';
 3import { createModalWindow } from './windows/modal';
 4
 5
 6 * 自动更新检测
 7 */
 8export const checkUpdate = () => {
 9  
10  function updateAvailable() {
11    
12    createModalWindow();
13  }
14  
15  function updateDownloaded() {
16    
17    
18  }
19  
20  autoUpdater.autoInstallOnAppQuit = true;
21  autoUpdater.autoDownload = true;
22
23  if (process.platform === 'darwin') {
24    
25    const ARM = process.arch == 'arm64';
26    const feedUUL = ARM
27      ? 'https://xxxx.com/installer/mac/arm'
28      : 'https://xxxx.com/installer/mac/intel';
29    autoUpdater.setFeedURL(feedUUL);
30  } else {
31    
32    autoUpdater.setFeedURL('https://xxxx.com/installer/win');
33  }
34  
35  autoUpdater.checkForUpdatesAndNotify().catch();
36  
37  autoUpdater.once('update-available', updateAvailable);
38  
39  autoUpdater.once('update-downloaded', updateDownloaded);
40};

2)在 .electron-builder.config.js 中加入 publish 字段,用于检测当前地址中latest.yml文件记录的版本

在加入了publish字段后我们再打包会生成一个latest.yml文件(苹果是latest-mac.yml),该文件我们需要上传到自己的安装包所在的目录,检测新版本就是检测 latest.yml文件中记录的版本

latest.yml 文件格式

完结

好了,到这里整个流程就算是跑完了,大家有不懂的可以在评论区讨论,文章哪里有写的不好的欢迎大佬指点,我太想进步了!!🥹

个人笔记记录 2021 ~ 2025