超完整的Electron打包签名更新指南,这真是太酷啦!

大家好,我是多喝热水。

在踩了数不清的坑之后,终于从 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-rebuildnode_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 文件格式

完结

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

相关推荐
酷酷的阿云7 分钟前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
微信:137971205879 分钟前
web端手机录音
前端
齐 飞15 分钟前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
神仙别闹32 分钟前
基于tensorflow和flask的本地图片库web图片搜索引擎
前端·flask·tensorflow
aPurpleBerry1 小时前
JS常用数组方法 reduce filter find forEach
javascript
GIS程序媛—椰子1 小时前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_0012 小时前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端2 小时前
Content Security Policy (CSP)
前端·javascript·面试
乐闻x2 小时前
ESLint 使用教程(一):从零配置 ESLint
javascript·eslint
木舟10092 小时前
ffmpeg重复回听音频流,时长叠加问题
前端