文章

Electron 在 Windows 下的交叉编译打包发布到 Linux Appimage 格式

Electron 在 Windows 下的交叉编译打包发布到 Linux Appimage 格式

TL;DR:

使用下面的命令利用Docker Desktop容器内的Linux环境在Windows上实现Linux安装包发布

1
2
3
4
5
docker run --rm -it `
  -v ${PWD}:/project `
  -v ${HOME}/.cache/electron:/root/.cache/electron `
  -v ${HOME}/.cache/electron-builder:/root/.cache/electron-builder `
  electronuserland/builder:node18-wine /bin/bash -c "npm install && npm run dist -- --linux appimage"

Electron 打包发布简介

Electron 的打包发布方案

针对 Electron 应用的打包和发布,主要有以下几种成熟的解决方案和工具:

🌈 自动化打包工具(推荐)

这是目前最常用和推荐的方式,它们将打包、生成安装程序/可分发文件、代码签名等流程高度集成和自动化。

Electron Forge

  • 定位: 致力于统一 Electron 的工具生态,提供一套可扩展的接口,简化打包和发布的流程。
  • 核心功能: 结合了 electron-packager 等核心模块,通过 make 命令可以一步完成打包 (package) 和生成可分发文件 (make)。
  • 支持格式: 可以配置生成各种系统特定的可分发文件,如 DMG (macOS), deb/rpm (Linux), MSI/exe (Windows) 等。
  • 代码签名: 支持在配置中添加证书信息,实现应用打包或生成可分发文件时的代码签名,这是应用被用户系统信任的关键步骤。

electron-builder

  • 定位: 一个完备的 Electron 应用打包和分发解决方案,致力于集成体验。
  • 核心功能: 内部管理了许多依赖项和要求,提供高度集成的打包和发布流程。
  • 特点: 在打包应用组件的集成度上较高,并且通常会替换或集成一些 Electron 维护者使用的模块(例如自动更新器)。

🔨 手动打包或底层工具

对于需要高度定制或更底层控制的场景:

使用 electron-packagerelectron-winstaller 等底层工具

electron-packager: 将应用代码与 Electron 二进制文件结合起来,生成一个可运行的文件夹/应用包(但不是安装程序)。你需要在此基础上使用其他工具来生成安装程序。

其他 Maker 工具: 例如,可以使用 electron-winstaller 或其他特定的工具来基于打包后的应用文件夹创建 Windows 安装程序 (MSI/exe)。

Electron 的打包发布流程

这里我们主要使用electron-builder作为案例来分析Electron项目的打包发布流程:

electron-builder 的核心流程可以概括为以下四个主要阶段:

阶段一:初始化与资源收集

  • 配置检查与目标确定:
    • electron-builder 读取 package.json 中的 build 配置,确定宿主操作系统(Host OS,即执行打包的系统,如 Windows)和目标操作系统(Target OS,如 macOS, Linux)。
    • 它会检查是否配置了 files 来包含所有必要的应用文件。
  • ⚠️ 原生依赖影响点(package.json):
    • dependencies 字段中列出的所有原生 Node 模块(例如使用了 C++ 扩展的模块,如 sqlite3, node-serialport 等)将被标记为需要进行跨平台重编译。

阶段二:应用打包与跨平台重编译

这是处理原生依赖的核心阶段。

  • 下载目标平台 Electron 运行时:
    • electron-builder 自动从 GitHub 下载对应目标平台和目标架构(如 macOS-arm64win32-ia32)的 Electron 预构建二进制文件。
  • 原生模块重编译:
    • electron-builder 会调用底层工具(如 electron-rebuild 或其内部机制),将第一阶段识别出的所有原生模块针对目标 Electron 版本和目标操作系统/架构进行重新编译。
    • 这是跨平台打包成功的关键。如果这一步失败,生成的应用将无法加载这些原生模块。
    • 某些复杂的原生模块可能依赖于目标系统的特定系统库或编译工具链。虽然 electron-builder 尽力自动化,但在 Windows 上为 Linux 或 macOS 编译某些复杂的 C++ 模块时仍可能遇到问题。
  • 代码封装:
    • 将重编译后的原生模块和应用代码一起打包成 asar 文件(或其他非 asar 格式)。

阶段三:可分发文件制作与代码签名

此阶段直接受限于宿主系统对目标系统签名工具的支持。

  • 生成安装程序:
    • electron-builder 根据配置(如 win 中的 target: ["nsis", "portable"]),使用内部或外部工具(如 NSIS, DMG Maker)生成最终的可分发文件。
  • 🔐 代码签名 (Code Signing): 这是跨平台打包最大的障碍所在。
    • macOS 签名: 制作 macOS 安装包(DMG/PKG)并进行 Apple Developer ID 签名,几乎必须在 macOS 宿主系统上完成。Windows 或 Linux 缺乏 Apple 专有工具和权限管理,无法可靠地进行 dmg 制作和 - notarization(公证)。
    • Windows 签名: 制作 Windows 安装包(EXE/MSI)并进行 Authenticode 签名,可以在 macOS 或 Linux 上完成,只要提供了有效的证书文件和密码。
    • Linux 签名: AppImage, Deb, Rpm 通常涉及 GPG 签名,这在不同宿主系统上相对容易交叉执行。

electron-builder跨平台打包能力总结

宿主操作系统 (执行打包的系统)目标平台 (要制作的安装包)是否支持 (开箱即用)
Windows“Linux (.deb AppImage 等)”✅ 是
Windows“macOS (.dmg .pkg)”⚠️ 有限支持/推荐借助 CI
macOS“Windows (.exe .msi)”✅ 是
macOS“Linux (.deb AppImage 等)”✅ 是
Linux“Windows (.exe .msi)”✅ 是
Linux“macOS (.dmg .pkg)”❌ 否

阶段四:发布与分发

自动更新依赖:

  • 生成的 artifact(安装包和 latest.yml / latest.json 等文件)的元数据是为 electron-updater 准备的。
  • 原生依赖影响: 如果你的应用需要在更新中切换到不同的原生模块版本(例如,从 x64 更新到 arm64),自动更新机制必须支持目标平台和架构的差异化下载。

实现跨平台打包的必要条件要成功进行跨平台打包,需要满足以下几个关键条件:

  • Node.js 原生模块的交叉编译: 如果你的应用使用了任何 Node.js 原生模块(C++ 扩展),electron-builder 需要能够为目标架构(如 x64, arm64)重新编译这些模块。
  • 目标平台的 Electron 二进制文件: electron-builder 会自动下载适用于目标平台(如 win32-x64, linux-arm64)的 Electron 预编译二进制文件。
  • 代码签名证书: 这是最关键的一步。在 Windows 上制作 macOS 包,需要 macOS 证书。在 macOS/Linux 上制作 Windows 包,需要 Windows Authenticode 证书。

为什么不建议优先使用跨平台打包

虽然 electron-builder 具备跨平台打包的能力,但官方和社区的最佳实践(Best Practice)始终是:在对应的目标运行平台上打包发布对应的应用

electron-builder 官网文档中关于多平台构建的说明,我们也可以看到, 其第一句话就说到:

不要期望你可以在一个平台上为所有平台构建应用程序。

“原生平台打包”优于“跨平台打包”的几个核心原因

🛡️ 无法绕过的签名与公证(尤其是 macOS)

  • macOS 的公证 (Notarization): 苹果要求所有分发的软件必须经过公证。这不仅需要代码签名,还需要调用苹果专有的 altoolxcrun notarytool 命令行工具。这些工具仅存在于 macOS 系统中。
  • Windows 的硬件签名: 如果你使用 EV (Extended Validation) 证书(存储在 USB 令牌上的证书),物理硬件必须插入正在打包的机器。在非原生环境下模拟这种硬件访问非常困难。

🏗️ 原生依赖 (Native Modules) 的不确定性

如果你的项目包含原生依赖(如 sqlite3, ffi-napi, sharp),跨平台打包会变得极其脆弱:

  • 编译环境模拟: 在 Windows 上为 Linux 编译原生模块,本质上是在 Windows 上模拟 Linux 的编译链(GCC, Python 等)。这种模拟很容易因为环境细微差别(如 GLIBC 版本不一致)导致打包出的程序在真实 Linux 上无法运行(报错 ELF header invalidsegmentation fault)。
  • 真实性验证: 在原生平台上打包,你可以立即在当前机器上运行生成的安装包,进行冒烟测试。而在跨平台打包时,你只能盲目相信打包工具,直到将包传到另一台机器上测试才能发现问题。

🛠️ 复杂的工具链依赖

为了实现跨平台,electron-builder 往往需要借助一些“桥接”工具,这些工具本身就增加了出错的概率:

  • WINE: 在 macOS/Linux 上打包 Windows 程序通常需要安装 WINEWINE 的版本兼容性、字体渲染、以及对某些复杂安装脚本的支持并不完美。
  • Mono: 处理某些 Windows 资源文件时可能需要 Mono 框架。

方案原理

虽然electron-builder项目组明确声明了优先使用原生平台打包, 但是这并不意味则我们必须使用单独的Linux或者MacOS的物理机才能完成Electron应用在对于平台的发布, 尤其是则路径虚拟化技术非常完善的时代.

这里我们完全可以使用物理机或者单独的CI流程使用云端序列环境完成Electron应用的打包发布, 但是考虑到这里我们的目标是在本地Windows开发计器上完成Linux发布包的生成, 我们完全可以使用使用Linux内核实现的Docker的容器环境完成项目的打包发布

electron-builder 官方文档和维护者更倾向于推荐使用 CI/CD (持续集成) 流程来解决跨平台需求,而不是在一台机器上完成所有平台的打包:

流程如下:

  • 开发者提交代码到 GitHub/GitLab。
  • 触发 CI 脚本,同时启动三个虚拟机(Runners):Windows RunnermacOS RunnerLinux Runner
  • 每个平台各司其职: macOS Runner 只负责打包、签名和公证 .dmg;Windows Runner 负责生成并签名 .exe
  • 最后将三个平台的产物汇总并发布。

对于商业化产品,通过 CI/CD 实现原生平台打包因为能自动化测试和发布, 是有更高稳定性和用户体验的选择, 对于测试发布和个人用途而言在本机搭建目标系统的虚拟环境更加方便和轻量

这里我们选择 Windows 上的 Docekr Desktop 完成 Linux 平台的安装包发布:

electron-builder的发布文档的末尾也提供了,使用 Docker 来构建 Linux 安装包的方案。

Windows 上的 Docekr Desktop 使用 WSL 2 作为默认的运行后端, 而 WSL 2 (Windows Subsystem for Linux 2) 提供的原生 Linux 内核环境,以及 Docker 容器的隔离和标准化构建能力.

electron-builder 在为特定操作系统(如 Linux)打包时,需要目标平台特定的系统依赖库、工具链和文件格式(如 AppImage, deb, rpm 等)。在 WSL 2 中,可以像在任何标准的 Linux 主机上一样安装这些依赖项。

通过在 WSL 2 中的 Docker 容器,electron-builder在打包的时候获得了一个原生、完整的 Linux 内核和预配置的构建环境。避免了在 Windows 主机上手动安装和配置复杂的 Linux 交叉编译工具链, 其运行环境完全等同于一个完整的Linux虚拟机或者物理机, Windows 宿主机仅作为文件存储和指令发起者,而将所有编译、依赖下载、原生模块构建(Native Rebuild)以及安装包封装的过程全部移交给 Linux 容器。

方案优势:

  • 环境对齐: Docker 容器运行的是真正的 Linux 发行版内核(通常是基于 Debian 或 Alpine)。这意味着容器内的 gccg++make 以及 glibc 都是 Linux 原生的。
  • 文件映射: 你通过 Docker 将 Windows 上的项目源代码文件夹挂载到容器内的路径(例如 /project)。
  • 原生构建: 当你在容器内运行 electron-builder 时,如果项目有原生依赖(如 node-canvassqlite3),它们会直接在容器的 Linux 环境下进行编译。生成的 .node 二进制文件是标准的 ELF 格式,而非 Windows 的 PE 格式。
  • 无缝产出: 打包完成后,electron-builder 生成的 .deb.AppImage.rpm 文件会直接出现在你 Windows 的 dist 文件夹中,因为该文件夹是共享挂载的。

打包实现流程

准备WSL2和Docker Desktop

WSL 2 依赖于Windows的Hyper-V虚拟化技术, 我们要首先检查Hyper-V的硬件兼容性:

首先在命令行中输入 systeminfo 检查验证硬件兼容性,然后检查 Hyper-V 要求部分,验证Windows中的硬件兼容性。如果所有列出的Hyper-V要求的值都为Yes(入下面所示),则系统可以运行Hyper-V

1
2
3
4
5
6
C:\WINDOWS\system32>

Hyper-V 要求:    虚拟机监视器模式扩展:是
                固件中已启用虚拟化:是
                二级地址转换:是
                数据执行保护可用:是

需要四项均为“是”才符合运行Hyper-V虚拟机的要求, 如果部分设置为我们需要进入主机的BIOS内修改虚拟化的支持选型, 具体操作流程可参考WSL 配置指南

在完成BIOS, 设置后我们需要在Windows功能中启用Hyper-V:

Hyper-V配置

在完成Hype-v 和 WSL 功能的开启后, 不需要手动管理 WSL 的子系统镜像。然后安装并运行 Docker Desktop 即可,Docker Desktop 会自动处理后续的所有配置。

安装 Docker Desktop:

  • Docker 官网下载并安装 Docker Desktop for Windows安装包
  • 双击Docker Desktop Installer.exe运行安装程序, 根据你选择的后端,在配置页面上选择“使用 WSL 2”选项(如有提示), 等待自动配置完成后即可使用Docker

启动electron-builder容器

使用以下控制台命令下载electronuserland/builder Docker 镜像

1
docker pull electronuserland/builder

然后从 Electron 项目的根文件夹(例如C:\MyApp),键入以下命令行命令创建容器并将 Electron 项目的根文件夹映射到打包容器的 /project 虚拟路径:

1
docker run --rm -ti -v C:\MyApp\:/project -w /project electronuserland/builder --name electron-builder

在容器中发布应用

在容器内部,输入以下命令来重新安装 Electron 项目的 npm 依赖包, 这一步很重要。因为有些解析的 npm 依赖包会根据项目和不同环境下安装不同的依赖

1
docker exec -it electron-builder bash
1
2
3
cd /project # 进入容器内的项目目录
npm i   # 安装NPM依赖
npm run build # 执行打包脚本(Package.json中配置好了打包脚本)

下面是一个完整的命令用于创建一次性容器来构建Liunx的Appimage安装包,, 可以直接使用这个命令来替代上面的操作:

1
2
3
4
5
docker run --rm -it `
  -v ${PWD}:/project ` # 映射当前项目目录源码到容器内
  -v ${HOME}/.cache/electron:/root/.cache/electron ` # 缓存Electron 运行时二进制文件。报错多次构建重复下载内容
  -v ${HOME}/.cache/electron-builder:/root/.cache/electron-builder ` # 缓存安装程序工具和元数据。
  electronuserland/builder:node18-wine /bin/bash -c "npm install && npm run dist -- --linux appimage" # 在容器内执行安装命令

安装Appimage

在容器中完成Build后, 项目的dist目录应该能看到一个发布的.AppImage文件, 这个时候我们可以将.AppImage文件复制到目标机器执行安装流程

赋予可执行的权限

这里我们可以使用chmod设置.AppImage文件的权限:

1
sudo chmod u+x ./Your.AppImage

或者在GUI中操作配置权限:

AppImage文件属性

右击文件,赋予可执行权限

AppImage修改权限

FUSE配置

AppImage需要FUSE才能运行, 如果没有安装FUSE直接运行AppImage会出现以下错误:

1
AppImages require FUSE to run. 

AppImage 运行为什么需要 FUSE ?

AppImage 需要 FUSE (Filesystem in Userspace) 是为了让一个单一的、压缩的 AppImage 文件能够在用户空间被当作一个完整的、可执行的文件系统进行挂载和访问,而无需 root 权限

FUSE(Filesystem in Userspace,用户空间文件系统)是一个 Linux 内核模块。它允许非特权用户(即普通用户,不是 root)编写自己的文件系统驱动,并在用户空间运行。

没有 FUSE,如果普通用户想要访问 AppImage 内的文件系统

  • 完全解压: 每次运行 AppImage 都必须将其完整解压到临时目录, 违背了 AppImage 即时运行的理念
  • 使用 Root 权限挂载: 传统上挂载 SquashFS 需要 root 权限。要求用户使用 sudo 运行一个 GUI 应用是非常不安全且用户体验极差的

FUSE安装:

Debian (>= 13) and Ubuntu (>= 24.04):

1
2
sudo add-apt-repository universe
sudo apt install libfuse2t64

Ubuntu (>= 22.04)

1
2
sudo add-apt-repository universe
sudo apt install libfuse2

完整安装可参考文档 Install FUSE

桌面图标配置

完成上述配置后, 就可以使用下面两者方式启动应用:

  • 右键双击
  • 以命令行方式运行./Your.AppImage

当两者可能都不太方便, 这个时候我们可以创建一个桌面快捷方式:

~/desktop目录下创建YourApp.desktop文件, 编辑文件,添加以下内容

1
2
3
4
5
6
7
8
[Desktop Entry]
Name=Your App # 名字
Comment=A Electron App #描述
Exec=/opt/your-app/Your.AppImage #文件绝对路径
Icon=/opt/your-app/app.png #图标绝对路径
Terminal=false
Type=Application
Categories=Developer

创建完后,在桌面右键图标,选择 允许运行

图标权限

到这里桌面快捷方式已经创建完了,不想放桌面的可以移动文件到菜单

1
2
# Ubuntu的菜单文件夹是 /usr/share/applications/
sudo mv Your.desktop /usr/share/applications/

完成这步,已经添加到菜单了,之后右击,选择添加到收藏夹,即可添加到侧边栏

参考

本文由作者按照 CC BY 4.0 进行授权