Electron 应用要交付给 Windows 用户,最终产物通常不是项目目录,而是一个 setup.exe 安装包。判断这件事能否做成,先看三件事:项目用的是 electron-builder 还是 electron-forge(两者生成的安装包类型不同,NSIS 与 Squirrel 的可定制项差别很大)、构建所在的机器是不是 Windows(在 Linux 或 macOS 上交叉生成 Windows 安装包需要额外工具链,签名也受限)、以及是否有需要跟着安装包一起走的原生模块或外部资源。这三点确认完,再动手写配置,返工概率会低很多。
打包 exe 安装程序的核心是把「运行所需文件清单」「安装行为」「版本与签名」三件事写进构建配置,而不是在代码里改路径。建议先在 Windows 机器上用 electron-builder 的 nsis 目标产出一个可安装的包,装到没有开发环境的机器上跑通,再考虑图标、安装目录选择、代码签名这些细节。如果缺少签名证书,安装包本身仍可生成和使用,但用户首次运行会遇到系统拦截提示,这一点需要提前和交付方对齐。
先确认当前场景和目标
不是所有「要一个 exe」的需求都需要安装程序。以下情况可以先判断清楚再决定形式:如果应用只是给内部几个人临时试用,直接输出 portable 或 zip 压缩包解压即用更省事;如果需要写注册表、创建桌面快捷方式、进入「应用和功能」列表供卸载,才应该做 NSIS 安装包。
动手前建议逐条核对:
- 构建机操作系统。在 Windows 上生成 Windows 安装包最直接;在其他系统上构建需要 wine 等兼容层,出问题时的排查成本明显更高。
- 是否含原生模块(node-gyp 编译的依赖)。原生模块的 ABI 必须和 Electron 版本对齐,否则安装后启动会直接崩在 require 阶段。
- 是否有代码签名证书。没有证书也能出包,但首次运行的系统拦截提示无法回避。
- 是否需要自动更新。自动更新通常要求安装包形态和更新源地址一起确定,后期从 portable 改成安装包会带来迁移成本。
- 目标用户是当前用户安装还是全机器安装。这决定安装时要不要管理员权限。
按这个顺序处理
下面是围绕 electron-builder 的通用处理顺序,工具选型不同可以替换,但步骤顺序基本一致。
第一步:补齐 package.json 的基础字段
name、version、description、author 这几个字段会被写进安装包元信息,缺失时部分配置会直接报错。把下面的片段替换成你自己的项目信息后放在 package.json 里,main 指向你的主进程入口文件。

{
"name": "my-desktop-app",
"version": "1.0.0",
"description": "示例桌面应用",
"author": { "name": "Your Team", "email": "dev@example.com" },
"main": "main.js"
}第二步:安装构建依赖并加脚本
在项目根目录执行,把 electron-builder 装成开发依赖,并新增一条打包脚本,避免每次手敲长命令。
npm install --save-dev electron-builder然后在 package.json 的 scripts 中加入:
{
"scripts": {
"start": "electron .",
"dist": "electron-builder --win nsis"
}
}第三步:写 build 配置
可以直接写在 package.json 的 build 字段里,也可以单独放一份 electron-builder.yml。下面这份配置的关键项是 files(决定哪些文件进包)、win.target(决定出安装包还是免安装包)和 nsis 段(决定安装行为)。directories.output 建议单独指定,避免和前端构建产物目录混在一起。

{
"build": {
"appId": "com.example.desktop",
"productName": "MyDesktopApp",
"directories": { "output": "release" },
"files": [
"main.js",
"preload.js",
"renderer/**/*"
],
"win": {
"target": ["nsis"],
"icon": "build/icon.ico"
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"artifactName": "${productName}-Setup-${version}.exe"
}
}
}几个容易踩的点:allowToChangeInstallationDirectory 只有在 oneClick 为 false 时才生效;perMachine 为 true 时安装需要管理员权限,面向普通用户的场景通常保持 false;icon.ico 建议准备多尺寸的图标文件,尺寸不足时构建阶段可能被提示或直接忽略。
第四步:如果有原生模块,先重建
含 node-gyp 编译依赖时,在打包前先按 Electron 的 ABI 重建,再执行构建。重建命令取决于项目使用的工具链,通常由 electron-rebuild 或构建工具自带的 install-app-deps 完成。跳过这一步,安装包能装成功但程序启动不起来。
第五步:执行打包
npm run dist命令结束后,去 directories.output 指定的目录找 *-Setup-*.exe,同时会生成一个用于增量更新的元数据文件。如果只看到 unpacked 目录而没有 exe,通常是 target 配置写成了 dir,或构建中途失败但终端输出被截断。
改完后怎么验证
验证不要只在开发机上做。把安装包拷到一台没有 Node.js、没有项目源码的 Windows 机器上,按下面的动作逐条确认:

- 双击安装包,确认安装向导能正常出现,且能选择安装路径(对应 oneClick=false 和 allowToChangeInstallationDirectory)。
- 安装完成后检查桌面和开始菜单是否出现快捷方式,快捷方式指向的路径是否在安装目录内。
- 启动程序,打开开发者工具或查看主进程日志,确认没有模块加载失败、静态资源 404 一类报错。这一步主要用来暴露 files 漏配和 asar 内路径问题。
- 在系统「应用和功能」里确认能看到该应用,卸载后检查安装目录和快捷方式是否被清理。
- 修改一次 version 后重新打包并覆盖安装,确认能升级而不是并存两个版本。
预期信号是:安装、启动、卸载三个环节都不需要人工干预,且安装目录下的 resources 里能看到你的应用包和额外资源。如果启动后白屏,先怀疑 files 没把渲染进程资源打进去;如果报找不到模块,先怀疑原生模块 ABI 不匹配。
容易忽略的边界
几个限制值得在动手前就知道,避免做到一半才发现方向不对:
- 跨平台构建的可靠性有限。在 Linux 或 macOS 上生成 Windows 安装包依赖额外工具链,签名步骤也往往无法完整执行,建议把 Windows 构建放在 Windows 机器或 Windows CI 上。
- 没有代码签名证书时,安装包可以生成,但用户首次运行会遇到系统安全提示,这个提示不能靠改配置消除,只能通过证书加签名缓解。
- asar 打包后,运行时通过相对路径动态读取的文件不会被自动包含,需要显式加进 files 或放到 extraResources,并在代码里改用对应的资源路径读取方式。
- 版本号只能递增。基于安装包的升级通常依赖版本号比较,手工把版本改小会导致升级逻辑异常。
- perMachine 模式和用户权限绑定较紧,企业内网批量部署和无管理员权限的终端上,安装行为会不一样,需要分别确认。
如果 NSIS 的自定义项暂时调不通,可以先回退到 portable 或 zip 形态交付,把安装流程放到下一轮再补;但要注意从免安装形态迁移到安装包时,用户数据目录和应用标识需要重新对齐,最好在早期就把 appId 定下来。