做过 Electron 开发的人基本都逃不过这一关:写代码一时爽,一到打包就翻车。尤其是 Windows 平台,本地 dev 跑得好好的,要产出能发给别人的 exe,中间的坑能写一本小册子。我这些年帮团队处理过不少 Windows 下的 Electron 打包问题,也踩过无数回自己埋的雷,从 electron-builder 配置到 serialport 这类原生模块,从 pnpm 兼容到 fpm 报错,几乎都经历了一遍。今天把 Windows 平台 Electron 打包的整套思路、核心配置和排查经验一次性讲清楚,给正在被安装包折磨的朋友一条能直接走通的路。
这篇文章适合两类人:一是用 Electron 做桌面应用、准备向 Windows 用户分发安装包的开发者,二是已经在打包但卡在白屏、原生模块报错、安装器被拦截等具体问题上的同学。看完你至少能搞定三件事:选对打包工具、写出一份可靠的 Windows 打包配置、遇到高频报错时知道先查哪里。
1. 先选对路子:Windows 下 Electron 打包方案怎么定
很多人一上来就搜"Electron 打包",搜出来一堆工具,反而懵了。其实 Windows 平台常用的方案就那么几种,区别主要在于最终产物形态、配置复杂度和对原生模块的支持情况。这里先把这个选择题做对,后面少走一半弯路。
1.1 electron-packager、electron-builder、electron-forge 怎么选
社区里现在最常被提到的三个工具是 electron-packager、electron-builder 和 electron-forge。它们不是一个东西,适合的场景也不太一样。
electron-packager 是最早的一批打包工具,核心能力就是把你项目的文件和一个对应平台的 Electron 二进制拼在一起,输出一个可执行文件夹。你双击里面的 exe 能跑,但没有安装界面,也没有开始菜单快捷方式。它适合极早期验证,或者企业内部直接发绿色目录的场景。不过指望它做出正式的分发安装包,还得再套别的工具,比较绕。
electron-forge 是 Electron 官方后来推的集成方案,把初始化、开发、打包、发布串成一条链路,默认集成了 Webpack 或 Vite 模板。如果你是完全新开项目,用它起步挺舒服。但它的封装更深,遇到 Windows 安装包定制需求时,想改 NSIS 这类底层行为,往往没有 electron-builder 那么直观。
electron-builder 是我个人长期在用的方案。它不是官方出的,但生态非常成熟,配置文件集中在一个字段或一个 yml 里,能直接产出 NSIS 安装包、portable 免安装 exe、zip 压缩包,同时也覆盖 macOS 的 dmg 和 Linux 的 AppImage、deb、rpm。对 Windows 平台来说,它的 NSIS 定制能力和原生模块处理机制,目前是三个方案里最让我省心的。
| 工具 | 主要产物 | 配置复杂度 | 原生模块支持 | 多平台覆盖 |
|---|---|---|---|---|
| electron-packager | 绿色可执行目录 | 低 | 需自己 rebuild | 支持 |
| electron-builder | 安装包 + 绿色目录 | 中 | install-app-deps 自动处理 | 支持 |
| electron-forge | 安装包 + 可执行目录 | 中 | 集成 rebuild | 支持 |
1.2 我一直用 electron-builder 的三个理由
第一,配置集中。我能用一份 electron-builder.yml 同时控制 Windows、Linux、macOS 的打包行为,不需要在各个工具脚本之间来回跳。对要长期维护的项目来说,这种"一处配置管全平台"的方式非常省事。
第二,对 Windows 安装包的控制力足够强。electron-builder 底层接 NSIS,我可以精确控制安装包是"一键安装"还是"传统向导",要不要允许用户改安装目录,要不要创建桌面快捷方式,甚至安装后的卸载程序行为。这些看起来是小细节,但对最终用户体验影响很大。
第三,原生模块处理省心。Windows 下最常出问题的就是 serialport、sqlite3 这类带 .node 文件的原生模块。electron-builder 提供了install-app-deps命令,会自动读取你项目里的 Electron 版本,并把原生模块重新编译成匹配 Electron ABI 的版本。这个机制帮我解决过大量"打包后模块崩溃"的问题。
1.3 先理清前端构建和 Electron 打包的分工
很多人容易把"前端构建"和"Electron 打包"混在一起。实际它们是两个阶段。
前端构建阶段,用 Vite、Webpack 这类工具把你的 Vue、React 代码编译成静态资源,输出到一个 dist 目录。Electron 打包阶段,electron-builder 只负责把主进程代码、预加载脚本、前端静态资源、依赖 node_modules 和 Electron 运行时组装成安装包。
所以项目里通常有两套配置并存:一套是前端脚手架的构建配置,一套是 electron-builder 的打包配置。两者之间唯一的联系就是 electron-builder 的 files 配置里要包含前端构建产物。想清楚这一层,后面遇到"打包后白屏""资源找不到"这类问题时,就能快速定位是构建阶段的问题还是打包阶段的问题。
2. 开打前先处理好三件事:环境、包管理器、ABI
正式跑打包命令之前,有三大前置问题必须先解决。很多人在这一步就卡住了,而且报错信息往往容易误导人,比如明明只是网络问题却显示成下载失败,明明只是 pnpm 的目录结构与打包器不兼容却提示找不到模块。
2.1 Windows 上真正需要装的工具只有这几样
打包 Electron 应用本身不需要装什么特殊环境,Node.js 是基础。我建议用 Node.js 的 LTS 版本,比如 18 或 20,太老的版本跑不动新版 electron-builder,太新的版本有时会出现一些依赖兼容问题。
如果你的项目里包含原生模块,那 Windows 上还需要 Visual Studio Build Tools。以前的教程经常提windows-build-tools一键脚本,但现在官方更推荐直接安装 Visual Studio 2022 Build Tools,安装时勾选"使用 C++ 的桌面开发"工作负载。否则执行 install-app-deps 时,node-gyp 会报gyp ERR! find Python或者找不到 MSVC 编译器。
另外 Git 不是必须的,但很多依赖源安装时需要它,装了能少一些莫名其妙的下载失败问题。代码签名工具(signtool)如果你不配证书,暂时用不上,后面第 5.4 节会专门讲 SmartScreen 的问题。
2.2 用 pnpm 的项目必须改 node-linker 配置
我现在的新项目基本都是 pnpm 管理依赖,因为省磁盘、安装快。但 electron-builder 对 pnpm 的支持一直有历史遗留问题,核心原因在于 pnpm 默认使用符号链接结构存放 node_modules,而不是像 npm 那样平铺。electron-builder 在收集依赖时,可能无法按预期找到某个包,表现出来就是打包后应用启动报"找不到模块 xx"。
解决办法是在项目根目录的.npmrc里加上这么一行:
node-linker=hoisted如果你用的 pnpm 版本较老,也可以用shamefully-hoist=true,效果类似,都是让 node_modules 结构更接近 npm 的平铺方式。改完之后记得删掉 node_modules,重新执行一次完整安装,否则配置不生效。
注意:不要为了省事跳过这一步。我见过有人用 pnpm 默认配置硬打,结果在本机怎么测都正常,换一台干净机器装上安装包就白屏或缺模块,最后排查了半天才发现是依赖结构问题。
2.3 native 模块能不能活下来,全看 Node ABI 对不对
ABI 这个词听起来深,其实可以这么理解:Node.js 原生模块编译出来的是一个 .node 文件,它和特定版本的 Node 运行时之间存在一套固定的二进制接口约定。Electron 内置的 Node 版本并不等于你本机装的 Node 版本,所以直接用你本机的 node-gyp 编译出来的原生模块,放进 Electron 里运行,十有八九会崩。
这是 serialport、robotjs、sqlite3 这类库打包后报错的根本原因。正确做法不是手动 node-gyp rebuild,而是使用 electron-builder 提供的命令:
npx electron-builder install-app-deps它会自动读取 package.json 里的 electron 版本,把 dependencies 中的原生模块重新编译成匹配 Electron ABI 的版本。每次升级 Electron 或改了原生模块版本后,都要重新跑一次这个命令,否则早晚会踩"Module did not self-register"这类错误。
3. 核心配置逐段拆解:用 electron-builder.yml 管住一切
环境问题解决之后,就可以进入真正的核心环节了:写配置。electron-builder 支持把配置写在 package.json 的 build 字段里,也支持独立文件。我推荐独立拆出一个 electron-builder.yml,因为打包配置在项目里会越加越多,放在 package.json 里会显得臃肿。
3.1 一份能直接用的 Windows 打包配置
下面是一份我在 Windows 项目里常驻的配置模板,覆盖了常规桌面应用的需求:
appId: com.example.myapp productName: MyApp directories: output: release buildResources: build files: - dist-electron/** - dist/** - package.json asar: true asarUnpack: - "**/*.node" win: icon: build/icon.ico target: - target: nsis arch: - x64 nsis: oneClick: false perMachine: false allowToChangeInstallationDirectory: true createDesktopShortcut: true createStartMenuShortcut: true shortcutName: MyApp artifactName: "${productName}-${version}-${arch}.${ext}" compression: maximum逐段说几个关键点。
appId 是应用唯一标识,建议用反域名格式。productName 是安装完成后在系统里显示的应用名,也是安装包文件名的组成部分。directories.output 指定安装包输出目录,我习惯用 release,这样不会和前端构建产物目录混在一起。
files 是打包内容的白名单,非常重要。这里我只让dist-electron(主进程构建产物)、dist(前端静态资源)和 package.json 进包。如果你不加这个字段,electron-builder 会把整个项目目录都收进去,本机 node_modules 里的开发依赖、测试文件、.env 全都可能被塞进安装包,既臃肿又危险。
asar 字段默认就是 true,作用是把应用代码打成一个 asar 压缩包,保护源码结构、减少文件数量。但原生模块需要单独处理,所以加了asarUnpack,把 .node 文件排除在 asar 之外。后面 serialport 那节会继续展开。
3.2 Windows 目标格式和架构怎么选
win.target 可以指定多个目标和架构。最常见的三个目标:
- nsis:标准 Windows 安装程序,支持安装目录选择、快捷方式创建、卸载入口,适合正式分发。
- portable:免安装的单 exe,运行时会自解压到临时目录,适合给非技术用户快速体验。
- zip:绿色压缩包,适合企业管理员批量部署或开发者自己分发。
架构方面,目前 x64 是主流,绝大多数 Windows 10/11 用户都是 64 位系统。如果用户群体里还有大量老旧电脑,可以同时打 x64 和 ia32。arm64 目前有需求但不多,主要针对 Surface Pro X 这类设备。建议用一个变量控制,避免把架构写死在代码里:
win: target: - target: nsis arch: - x64artifactName 里的${arch}就是用来区分架构的,否则 x64 和 ia32 版本同名,发布时就乱了。
3.3 NSIS 安装体验调优:从"能装"到"好装"
NSIS 配置是 Windows 安装包体验的关键。很多人打出来的安装包双击后直接装了,用户连装到哪里都不知道,卸载时也找不到入口,体验很差。我一般会关掉 oneClick,开启传统安装向导模式。
oneClick 设为 false 后,用户安装时会有"选择安装目录""是否创建快捷方式"这些步骤,对普通用户更友好。allowToChangeInstallationDirectory 设为 true,配合前面那个设置,用户才能真正改安装目录。perMachine 我通常设置为 false,这样默认安装到当前用户目录,不需要管理员权限,也不会触发 UAC 弹窗;如果产品需要安装到 Program Files,那就要设为 true,并接受 UAC 提权。
还有一个容易忽略的点:shortcutName 最好和 productName 保持一致,否则用户安装完找不到应用名字对应的快捷方式,会以为安装失败了。
注意:NSIS 安装路径尽量不要让用户选中文或特殊字符目录。虽然 Windows 能创建中文明目录,但后续应用读写文件时,个别原生库会在路径编码上出问题,这个坑比较隐蔽。
4. 完整实操流程:从项目目录到能分发的安装包
配置写完之后,实际操作其实就几条命令的事,但每一步都有值得注意的细节。我第一次打 Windows 包时,因为不知道要看 win-unpacked 目录,直接在安装包装上踩了半小时,后来逐步形成了一套标准流程。
4.1 首次打包两条命令跑通,先看 unpacked 目录
安装 electron-builder 后,先在 package.json 里配好脚本:
{ "scripts": { "build:app": "vite build", "pack:dir": "electron-builder --dir", "pack:win": "electron-builder --win --x64" } }第一次执行打包时,不建议直接打安装包,而是先跑electron-builder --dir。这个命令不会生成 NSIS 安装程序,只会生成一个win-unpacked目录,里面就是解压后的完整应用。你可以直接运行里面的 exe,验证主进程逻辑、前端资源、原生模块是否正常。
确认 win-unpacked 里的应用没问题后,再执行pack:win产出正式安装包。这样能把"应用本身有问题"和"安装包制作有问题"两个环节隔离开,排查时思路清晰很多。
首次打包还需要下载 Electron 二进制和 NSIS 工具,这一步最容易卡住。如果长时间停留在下载阶段,可以先设置镜像源:
$env:ELECTRON_MIRROR = "https://npmmirror.com/mirrors/electron/" $env:ELECTRON_BUILDER_BINARIES_MIRROR = "https://npmmirror.com/mirrors/electron-builder-binaries/"设置后重新执行打包命令,下载进度会明显变快。下载完的文件会缓存在%LOCALAPPDATA%\electron-builder\Cache,后续打包不会再重复下载。
4.2 图标、版本号和文件属性一次配到位
如果你不配置图标,安装包和应用 exe 会使用 Electron 默认图标,一眼就能看出来不是专业产物。Windows 平台对图标格式有要求,必须用 .ico 文件,且建议包含 256x256 尺寸。很多设计工具直接导出的 png 不能直接用,得先转成 ico。
拿到 icon.ico 后放到 build 目录下,在 win 配置里指定:
win: icon: build/icon.ico版本号方面,electron-builder 会自动读取 package.json 里的 version 字段,所以一定让 version 和应用实际版本同步。除此之外,可以通过win.signAndEditExecutable相关的配置来写入文件属性信息,比如公司名、产品描述。做企业内部分发时,这些细节会让系统属性里的信息看起来正规很多。
4.3 serialport 这类原生模块的正确打包姿势
serialport 是很多硬件桌面项目绕不开的库,也是 Electron 打包问题里出现频率最高的一类。它的问题集中在两个点:ABI 编译和文件路径。
ABI 编译前面讲过,打包前一定要执行npx electron-builder install-app-deps。不要手动执行 npm 的 rebuild,否则编译出来的版本只匹配本机 Node,不匹配 Electron。
文件路径问题在于,serialport 编译出来的 .node 文件如果被打进 asar 压缩包,运行时无法直接加载。所以配置里要加 unpack 规则:
asarUnpack: - "**/*.node" - "**/node_modules/serialport/**"这里把 serialport 整个模块目录都排除出 asar 了,确保它的 .node 文件和动态依赖能被安全访问。改完配置后重新打包,一般能解决大多数 serialport 相关报错。
如果还是不行,多半是 serialport 的依赖里缺少 vcruntime140.dll 这类运行库,或者安装了非官方精简版系统的用户环境里缺运行库。遇到这种情况,可以把需要的 dll 放到 extraResources,再在应用启动时手动加载,但这种情况相对少见。
4.4 白屏和布局异常,绝大多数卡在这里
Electron 应用打包后白屏是出现率最高的前端问题,十次里有八次是路由模式导致的。开发环境用的是 localhost 地址,路由用 createWebHistory 没问题;但打包后应用通过 file:// 协议加载本地文件,history 路由会直接失效,页面空白。
解决办法有两个:简单粗暴的是把前端路由改成 createHashHistory,另一种是保留 history 模式,但把前端构建的 publicPath 配成相对路径,并在 Electron 里用自定义协议加载。前者实现快,后者体验更接近标准 web 应用,都可以。
热词里还有"vue 打包后 布局异常"这类问题。我排查过的一个典型案例是:CSS 里引用的图片路径是绝对路径,打包后在本地 file:// 协议下加载失败,导致样式失效、布局全乱。把构建配置里的 base 或 publicPath 改成相对路径,绝大多数这类问题都能解决。
所以遇到白屏或布局异常,先别急着怀疑 electron-builder 配置,重点检查三样东西:路由模式、构建 publicPath、资源引用路径。用electron-builder --dir生成目录后,可以直接在本地运行 exe,打开开发者工具看 Console 报错,比反复打安装包高效很多。
5. 高频报错与排查实录,直接对着症状找答案
下面这些报错和现象,是 Windows 打包里最常见的。我把它们整理成问题排查表,你在实际操作中可以直接对照,省掉到处搜的功夫。
5.1 卡在下载 winCodeSign / nsis,是网络而不是配置错了
很多初次打包的人看到Downloading winCodeSign或Downloading nsis卡住,以为配置写错了,实际上只是 electron-builder 从 GitHub Releases 下载辅助工具失败或特别慢。这类问题的典型日志是反复重试后报 timeout 或 404。
解决办法就是前面提到的设置镜像源。如果公司内网有统一的 npm 镜像,也可以把 electron 和 electron-builder 的二进制镜像一起配置进系统的环境变量,团队所有人共享。
另外,既然下载工具卡顿是常态,我在团队内部会建议维护一个"打包缓存目录"的备份。把%LOCALAPPDATA%\electron-builder\Cache里已经下载好的文件压缩存档,换新机器时直接解压到对应目录,能省不少时间。
5.2 遇到 fpm 相关报错,先别急着重装环境
fpm 报错在很多打包场景里都会出现,但它往往不是因为你的 Electron 配置有误,而是因为你试图在某个平台上去构建另一个平台的安装包,或者构建 deb/rpm 包时依赖不对。
fpm 早期是 electron-builder 在 Linux 下生成 deb/rpm 包时依赖的外部工具,如果你的 Linux 环境缺少 Ruby 和 fpm,就会看到类似cannot exec fpm或fpm failed的报错。新版 electron-builder 已经内置了部分能力,但老项目或特定发行版仍可能遇到。
更常见的情况是,有人在 Windows 上尝试直接打 Linux 包,然后遇到 fpm 系列问题。这是最不建议做的事。Windows 平台老老实实打 Windows 包,Linux 包交给 Linux 环境或 CI 去完成,macOS 包也同理。跨平台打包看似方便,遇到了坑才知道代价更大。
5.3 原生模块打进包后仍报错,按顺序查三点
如果 serialport 或者其他原生模块在 win-unpacked 里运行时报错,按下面顺序排查,基本能定位:
第一,先看报错里有没有was compiled against a different Node.js version或Module did not self-register。有的话,几乎可以确定 ABI 没对上,重新执行electron-builder install-app-deps。
第二,检查 .node 文件是否进了 asar。把 win-unpacked 里的 resources/app.asar 解包看一下,如果 serialport 的 .node 文件在 asar 内部,就要调整 asarUnpack 配置,重新打包。
第三,检查系统运行库。在干净的 Windows 虚拟机里测试,如果报错提示缺 dll,说明用户环境可能缺 Microsoft Visual C++ Redistributable。这种情况要要么在安装包里带上运行库,要么在文档里标注前置要求。
5.4 SmartScreen 拦截安装包,个人开发者怎么应对
“Windows 已保护你的电脑”这个提示,用 Electron 分发过应用的人都见过。原因很简单,你的 exe 没有代码签名证书,Windows Defender SmartScreen 对发布者不明的程序默认不信任。
如果你只是内部工具,没有证书可以暂时不处理,但要让团队知道安装时可能需要点击"更多信息"然后选择"仍要运行"。如果你要公开发布,我建议认真考虑买代码签名证书,普通 OV 证书和 EV 证书差价不小,但 EV 证书能让 SmartScreen 更快积累信誉。
有一点必须强调:不要去网上找那些所谓的"免杀处理""过白名单"手段,这类操作既不专业也有合规风险。正路就是证书签名,或者先通过开源发布让应用积累用户反馈和信誉。SmartScreen 检测的是数字签名信任体系,常规发布手段短期有提示是正常的,坚持正规分发声誉会逐步建立起来。
5.5 安装包异常大、.env 和源码被打进去,怎么查
进程构建产物通常只有几兆,但如果你 files 配置没写好,把整个项目目录包含进去,node_modules 里开发依赖那些东西会被全部打进去,安装包随随便便超过 150MB,甚至 300MB。
排查方法很直接:用electron-builder --dir打包出目录后,进win-unpacked/resources/查看 app.asar 的体积,再用npx asar list app.asar查看文件清单。重点检查有没有出现 .env、.git、src 目录、测试文件等不该出现的文件。
一旦发现 .env 被打进包,立刻处理:把 .env 加入 .gitignore 只是第一步,还要在 electron-builder 的 files 配置里显式排除,并且轮换掉所有已泄露的密钥。这件事千万别拖,我在实际工作中见过不止一次因为打包配置宽松导致密钥外泄的事故。
5.6 安装不完整或升级失败,常见的用户侧原因
热词里大量出现"xx windows 安装未完成"类似问题,Electron 应用自己也会有这个现象。绝大多数情况不在打包本身,而在安装和升级过程。
安装不完整,常见于杀毒软件把安装包释放的临时文件拦截了,或者安装目录没有写权限。NSIS 安装包在解压过程中被安全软件扫描到异常行为,直接杀掉进程,就会出现"安装到一半失败"的表现。
升级失败,常见于旧版本应用还在运行,安装程序尝试覆盖文件时发现文件被占用。所以做升级逻辑时,先引导用户退出旧版本,再启动新安装包。如果是 perMachine 模式,还要确保用户有管理员权限,否则 UAC 提权失败也会中断安装。
6. 多平台分发与发版前的自检清单
Electron 的好处是写一套代码能跑三个平台,但"能跑"不代表"能打包到全平台"。尤其是 Windows、Linux、macOS 的打包环境差异很大,处理好这些差异,才能让分发流程稳定不折腾。
6.1 想同时出 Windows / Linux / macOS,在 CI 里各打各的
electron-builder 支持一条命令同时指定多平台,比如electron-builder --win --linux,但这个做法我不推荐你直接在本机执行,尤其是 Windows 上交叉打 Linux 包。deb/rpm/AppImage 需要 Linux 工具链,macOS 的 dmg 更是只能在 macOS 上完成。
更稳妥的做法是走 CI,比如 GitHub Actions 或公司的 Jenkins。Windows 上跑 Windows 打包任务,Ubuntu 上跑 Linux 打包任务,macOS 上跑 macOS 打包任务,各平台各打各的,产物互不干扰。这样也方便在发布时自动生成各平台的安装包,统一上传到 realease。
如果你用 GitHub Actions,workflow 核心思路大致是先 checkout 代码,再安装依赖,然后执行对应的打包命令:
- name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npx electron-builder --win --x64这样一套配置维护一份,三个平台都能持续产出,也避免了本机交叉编译的许多历史遗留坑。
6.2 我每次发版前都会过一遍的 8 项检查
经常打 Windows 包之后,我形成了一个比较固定的发版前检查清单,每次照着走一遍,能少犯很多低级错误。
| 检查项 | 操作方式 | 常见问题 |
|---|---|---|
| 主进程加载路径 | 确认开发路径和生产路径分开 | 用了 file:// 或本地服务器方式不一致导致白屏 |
| 前端路由模式 | 确认生产环境用 hash 或自定义协议 | history 模式在 file:// 下白屏 |
| 资源引用路径 | 构建产物 publicPath 设成相对路径 | 绝对路径导致图片、CSS 加载失败 |
| 原生模块 ABI | 执行 install-app-deps | 原生模块报 NODE_MODULE_VERSION 错误 |
| asar 文件清单 | 用 asar list 抽查 | .env、源码、测试文件混入 |
| 安装包体积 | 观察是否出现异常膨胀 | files 配置过宽,开发依赖混入 |
| 干净环境安装测试 | 用虚拟机或新用户测试 | 缺运行库、SmartScreen 拦截、安装路径异常 |
| 升级覆盖测试 | 旧版本运行中安装新版本 | 文件被占用、版本回退、配置丢失 |
这套清单我从一开始的"想到什么查什么",慢慢迭代成了现在的固定模板。做完再发布,心情都会稳不少。
最后再分享一个我自己摸索出来的习惯:每次打包前,先花一分钟看一下 electron-builder 的版本和 Electron 的版本,别让这两个核心依赖长期停留在很旧的版本上。Electron 升一个大版本,Builder 也要跟着升,否则很容易出现新版 Electron 和旧版 Builder 不兼容的问题。打包这个事,做到后期拼的其实不是花活,而是把每一步该确认的事情老老实实确认完。希望这篇文章能帮你少走几步弯路,早点把精力放回你的产品本身。