先说个真实场景,我一个项目在 Linux 下用 electron-builder 打 deb 包,结果每次都卡在 Downloading fpm 这一步,有时候十几分钟一动不动,有时候直接超时失败。查了半天发现 electron-builder 默认要跑到 GitHub 上去拉 fpm 这个工具,网络不给力的时候就完全卡死。后来通过自定义 fpm 镜像地址,把下载源切到国内镜像,十几秒就完事。这篇文章就把完整思路和实操过程整理出来,给同样被这个问题折磨的人一个参考,尤其是需要频繁在 Linux 环境打 deb、rpm 包的 Electron 开发者。
1. 先搞清楚:electron-builder 为什么非要下载 fpm
1.1 fpm 到底是什么角色
fpm 的全称是 Effing Package Management,一个用 Ruby 写的开源工具,核心能力是把一个已经构建好的目录快速转换成 Linux 下的各种软件包格式,比如 deb、rpm、pacz 等等。在 electron-builder 的打包流程里,它的角色相当于是最后一步的「格式转换器」:当你执行electron-builder --linux deb时,electron-builder 会先把你的应用代码打包成 asar 归档,再整理出一个临时目录结构,然后调用 fpm 把这个目录转成 deb 安装包。
这里有个容易混淆的点需要澄清:网上很多文章把 fpm 和 electron-builder 自带的 app-builder 混为一谈,但其实是两回事。app-builder 是 electron-builder 官方用 Go 写的辅助二进制,负责处理文件拷贝、图标转换、deb/rpm 包内部元数据生成等;而 fpm 是外部工具,主要承担「目录到包格式」的转换。两者在打包时都会被调用,但镜像配置走的是同一条环境变量路径。
如果你只是打 AppImage 或者免安装的 zip 包,那就用不到 fpm。但只要你的 target 里有 deb 或者 rpm,fpm 就一定绕不过去。很多开发者一开始以为装完 electron-builder 就万事大吉,没想到它还要额外下载这么多依赖工具,这就是问题迟迟没被发现的原因之一。
1.2 下载流程和缓存机制
electron-builder 在需要 fpm 时,会先检查本地缓存目录里有没有对应版本的 fpm。缓存路径根据系统不同有差异:
- macOS:
~/Library/Caches/electron-builder - Linux:
~/.cache/electron-builder - Windows:
%LOCALAPPDATA%\electron-builder\Cache
如果缓存里没有,它就会拼接一个 URL 去下载。默认拼出来的下载地址大概是这样的结构:
https://github.com/electron-userland/electron-builder-binaries/releases/download/ + fpm-1.9.3-2.3.1-linux-x64/ + fpm-1.9.3-2.3.1-linux-x64.7z注意文件名里带了一长串版本号,electron-builder 在每次构建时都会根据内部依赖版本动态拼出完整文件名,并且还会用 SHA256 做完整性校验,确保下载下来的二进制没被篡改过。所以如果你想手动把 fpm 文件放进缓存目录来绕过下载,文件名必须和日志里显示的完全一致,版本号多一位少一位都不行。
理解了它「默认从 GitHub Release 下载」这个机制,你就明白了为什么会慢、为什么会失败。在部分网络环境下,GitHub Release 下载经常超时,加上 fpm 压缩包大概有 60MB 左右,一旦断断续续,体验特别糟。解决方案的思路也直接:把拼接 URL 的前缀替换成一个国内可达的镜像地址,路径后面的部分保持不变,这样下载速度和稳定性都会有质的提升。
2. 换镜像的几种主流方式
2.1 环境变量最直接有效
electron-builder 官方支持一个专门的环境变量,叫ELECTRON_BUILDER_BINARIES_MIRROR。这个变量就是用来替换刚才提到的那段 GitHub 前缀的。设置了它之后,所有通过 electron-builder-binaries 下载的二进制文件,包括 fpm、winCodeSign、nsis、app-builder 等,都会从这个镜像地址去拉。
在 Linux/macOS 下,你可以临时在终端里试一下:
export ELECTRON_BUILDER_BINARIES_MIRROR=https://npmmirror.com/mirrors/electron-builder-binaries/ electron-builder --linux deb在 Windows PowerShell 下则是:
$env:ELECTRON_BUILDER_BINARIES_MIRROR="https://npmmirror.com/mirrors/electron-builder-binaries/" electron-builder --linux deb但这样每次都要手动敲一遍环境变量,很麻烦,而且换一台机器就忘了。更推荐的做法是把环境变量固化到 npm scripts 里。这里有一个跨平台的小坑:在 Linux/macOS 上可以用KEY=value直接作为命令前缀,在 Windows 的 cmd 或 PowerShell 里却不支持这种写法。所以最稳妥的方案是用 cross-env 来统一注入环境变量。
{ "scripts": { "build:linux": "cross-env ELECTRON_BUILDER_BINARIES_MIRROR=https://npmmirror.com/mirrors/electron-builder-binaries/ electron-builder --linux deb" } }先安装 cross-env,再运行npm run build:linux就行了。
npm install -D cross-env我在实际项目里踩过一个教训:当时图省事,只在自己终端里 export 了变量,结果配置只在我机器上生效,同事那边照样卡在 GitHub 下载。CI 上更严重,每次构建都等超时才失败。后来把配置写进 npm scripts 里,全团队才统一了行为。所以凡是涉及网络下载源的配置,一定要落到项目文件里,不要依赖个人终端环境。
2.2 .npmrc 配置文件方案
环境变量写进 npm scripts 已经够用,但每次看到那一长串前缀还是觉得有点啰嗦。另一个方案是把镜像源写进项目根目录的.npmrc文件里。npm 在运行 scripts 时,会自动把.npmrc里key=value形式的配置项转换成npm_config_前缀的环境变量注入进程,而 electron-builder 在读取配置时,也会去查这些npm_config_变量。
所以可以新建一个.npmrc,内容如下:
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/ electron_mirror=https://npmmirror.com/mirrors/electron/这样之后 npm scripts 里的 electron-builder 就能自动读到镜像配置,无需在 scripts 里重复写环境变量。这个文件放在项目根目录,并建议提交到 Git 仓库,这样团队成员拉下代码后,打包行为天然一致,省得每个人去手工配环境变量。
这里提一下原理,其实 electron-builder 不是自己发明了一套「读 npmrc」的机制,而是 npm 在执行脚本时把所有配置项都暴露成了环境变量。electron-builder 底层在查找镜像地址时,会同时检查process.env.ELECTRON_BUILDER_BINARIES_MIRROR和process.env.npm_config_electron_builder_binaries_mirror。所以无论你走哪条路,最终殊途同归。
有些人可能会问,能不能在 package.json 的build字段里直接配置镜像地址?实测下来,electron-builder 并没有给 fpm 提供独立的配置项,它只有electronDownload这种针对 Electron 框架包下载的配置,而构建辅助二进制还是只能走环境变量。网上有些资料说往 yml 里塞 mirror 配置就能生效,那多半是把 electronDownload 的配置当成 fpm 了,我劝你别直接照抄。
3. 一次完整的打包配置实操
3.1 环境准备
在动手之前,先确认你的本机工具链是完整的。有人以为 fpm 既然是 Ruby 写的,就得先装 Ruby 环境,其实不需要。electron-builder 下载的是预编译好的 fpm 二进制,里面自带了运行所需的依赖,并不依赖系统 Ruby。所以你的机器上只需要 Node.js、npm 正常,基本就够了。
electron-builder 本身建议用较新的版本,老版本比如 20.x 的下载逻辑、缓存目录结构和新版有一些差异,很多镜像兼容问题其实就是版本太旧导致的。进入正题前,先把依赖装好:
npm install -D electron electron-builder cross-env然后准备一个最小化的package.json来演示,重点看结构和关键字段,不要直接照搬生产配置:
{ "name": "demo-app", "version": "1.0.0", "description": "electron-builder fpm mirror demo", "main": "main.js", "scripts": { "build:deb": "cross-env electron-builder --linux deb" }, "devDependencies": { "electron": "^31.0.0", "electron-builder": "^24.13.3", "cross-env": "^7.0.3" }, "build": { "appId": "com.example.demo", "productName": "DemoApp", "files": ["main.js", "index.html"], "linux": { "target": ["deb"], "category": "Utility" } } }3.2 配置镜像并触发构建
在项目根目录创建.npmrc:
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/ electron_mirror=https://npmmirror.com/mirrors/electron/然后执行构建命令:
npm run build:deb第一次构建时,终端会看到类似的日志:
Downloading electron-builder-binaries/fpm-1.9.3-2.3.1-linux-x64.7z如果配置生效,日志里显示的下载源会带上 npmmirror.com 的地址。下载完成后,会自动解压并校验,进入打包流程。如果日志里显示的还是 github.com,说明配置没被读到,优先检查.npmrc文件名、位置是否正确,以及环境变量是否被覆盖。
提示:electron-builder 的普通日志里不会直接打印完整下载 URL,只显示文件名。想确认实际请求的地址,可以给构建命令加上
DEBUG=electron-builder*环境变量再跑一次,日志会详细很多。
3.3 验证缓存,确认产物
打包成功后会生成dist/目录,里面有类似demo-app_1.0.0_amd64.deb的文件。可以用dpkg -c看一下内容,确认基本结构没问题:
dpkg -c dist/demo-app_1.0.0_amd64.deb | head -20同时去缓存目录里看一眼 fpm 是否已经存在:
ls ~/.cache/electron-builder/fpm/正常能看到一个以fpm-1.9.3-2.3.1-linux-x64命名的目录。以后再次构建时,只要版本不变,electron-builder 会直接复用本地缓存,不再发起网络下载,打包速度会快很多。所以第一次构建是最慢的,后面体验会好很多。
这里我还想强调一个细节:镜像地址末尾的斜杠很关键。https://npmmirror.com/mirrors/electron-builder-binaries/和https://npmmirror.com/mirrors/electron-builder-binaries虽然看起来差不多,但 electron-builder 拼接 URL 时是做简单字符串连接,如果少了末尾的/,最终 URL 会变成.../electron-builder-binariesfpm-1.9.3...,直接 404,而且报错很隐蔽,不会提示你哪里拼错了。这是我第一次配置时踩过的坑,写出来给大家避雷。
4. 常见问题与排查实录
4.1 下载 404 或 403,到底是谁的问题
镜像配置好了,但还是报 404,很多人第一反应是镜像源有问题,其实大部分情况是 URL 拼接错了。遇到这种问题,先把最终下载 URL 打出来看,不要瞎猜。可以用DEBUG=electron-builder*跑一遍,或者干脆手动打开日志里提示的路径,看看文件是不是真的存在。
如果确认是 URL 问题,排查顺序参考下面这个表格:
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| URL 路径里缺了目录或者多了一段字符串 | 镜像地址尾部没加/ | 确保镜像地址以/结尾 |
| URL 里出现了不同协议混用或重复前缀 | 环境变量里带了引号或空格 | 检查.npmrc或cross-env的写法 |
| 镜像地址正确但文件仍不存在 | 镜像站同步滞后,没有这个版本 | 换个同步及时的镜像源,或手动下载放入缓存 |
还有一个容易被忽略的场景:如果你的项目是 monorepo,可能存在多个 package 各自安装了 electron-builder,导致实际执行的命令来自某个缓存版本,而配置项又被隔离了。排查时先看node_modules/.bin/electron-builder指向哪个版本,再用该版本对应的配置逻辑去判断,不要在一个 package 里配了,却在另一个 package 里执行打包。
4.2 校验和 checksum 不一致
electron-builder 下载完 fpm 之后会做 SHA256 校验。如果你用的镜像文件本身没问题,但下载过程中被某些代理工具改写,或者镜像站做了二次压缩,就很容易出现校验不一致的报错。这类报错通常会带有make sure that the package is not corrupted这类关键词。
我的建议处理流程:
- 清空本地 fpm 缓存目录:
rm -rf ~/.cache/electron-builder/fpm - 换一个更稳定的镜像源,或者用官方 GitHub Release 手动下载,完成后手动放入缓存目录
- 如果是在公司内网环境,让网络管理员放行对应的下载域名,不要走会改写文件的代理网关
这里专门提醒一下:千万别为了跳过校验而关闭 checksum 验证。打包工具是在构建机上执行外部二进制的,如果下载的文件被替换成恶意版本,后果比下载慢严重得多。镜像配置为了省时间可以理解,但安全校验这条底线最好不要动。
4.3 缓存残留导致的奇怪构建问题
还有一种情况比较隐蔽:fpm 下载成功了,但接下来构建报某个库文件找不到,或者生成 deb 包之后安装到系统里发现运行异常。这类问题很多时候是缓存目录里残留了旧版本或者损坏的解压文件,而 electron-builder 只负责校验压缩包本身,不会去校验解压后的目录。
我遇到莫名奇妙的打包失败时,习惯按下面步骤来:
- 清空 electron-builder 的整个缓存目录,不仅是 fpm 子目录,因为 app-builder、winCodeSign 这些工具的缓存也可能有问题。
- 删除
dist/目录,避免旧产物干扰判断。 - 在干净环境下重新跑一次构建,观察第一条日志输出。
rm -rf ~/.cache/electron-builder rm -rf dist缓存目录不属于项目源码,删了不会影响代码,放心操作。有时候这个「暴力清缓存」反而是最有效的排查手段。
5. 配套的镜像组合拳
5.1 别只配 fpm,Electron 框架包也得配镜像
很多人配好ELECTRON_BUILDER_BINARIES_MIRROR之后,发现第一次打包还是慢,那是因为 electron-builder 除了要下载 fpm,还要下载 Electron 框架的发行包。这个包比 fpm 大得多,动辄上百 MB,如果它走默认的 GitHub 地址,照样卡到你怀疑人生。
对应方案是配置ELECTRON_MIRROR,指向 Electron 安装包的镜像地址:
electron_mirror=https://npmmirror.com/mirrors/electron/在.npmrc里加上这一行即可。这里有个双保险的效果:不仅 electron-builder 下载 Electron 发行版时会走镜像,electron这个 npm 包在 postinstall 阶段下载二进制时也会读ELECTRON_MIRROR环境变量,所以这行配置同时管了两个环节。很多人只配了 fpm 镜像,忽略了这个变量,结果每次安装依赖后第一次跑 electron 还是慢得离谱。
5.2 其他辅助二进制一并覆盖
electron-builder 在构建不同平台目标时,还会用到其他辅助二进制,比如:
winCodeSign,Windows 代码签名工具,构建 win 目标时需要nsis,生成 Windows 安装包时需要app-builder,处理整个打包流程的通用辅助工具
这些工具的默认下载地址同样是 GitHub Release,对应的镜像环境变量是同一个ELECTRON_BUILDER_BINARIES_MIRROR。所以只要你这一个变量配好,fpm、winCodeSign、nsis 等都会一起走镜像,属于一箭多雕,不需要为每个工具单独配置。
如果你用的是 GitHub Actions 或者 Jenkins 这类 CI 平台,建议把镜像环境变量直接写在 CI 的环境变量配置里,而不是在构建命令里临时传。因为 CI 容器每次都是全新环境,缓存目录不稳定,提前把镜像配置固化在环境层,能减少很多不必要的网络问题。
写在最后的一个小技巧
原本只打算写 fpm 镜像配置,但真正上手之后你会发现,electron-builder 的下载链路里,fpm 只是其中一环。我现在已经养成习惯,不管当前项目只需要打 deb 还是 rpm,electron_builder_binaries_mirror和electron_mirror两个配置永远同时出现在.npmrc里,先写上再说,防止哪天换了目标格式就突然卡一下。
踩过几次坑之后,我总结发现:凡是出现「网上配置了但不生效」的问题,八成是环境变量名拼错了,或者 cross-env 没有成功注入。你可以写一个临时脚本,先打印一下process.env.ELECTRON_BUILDER_BINARIES_MIRROR,确认配置真的传到了 electron-builder 所在的进程,再往下排查,能少走很多弯路。镜像配置本身不难,难的是把下载链路的每一环都排查明白。照上面的步骤走一遍,基本就能告别 fpm 下载慢、下载失败的烦恼了。