打包桌面应用时,静态资源(图片、字体、音视频)和原生资源(.node 原生模块、外部可执行文件、动态库等)的处理往往比业务代码更让人头疼。这一节用最直接的方式告诉你,哪些资源需要手动配置、怎么配,以及最常见的坑该怎么避。
18.4.1 静态资源的默认行为与路径调整
Electron 应用打包后,默认不会改变你的资源文件路径。electron-builder 会将 package.json 所在目录(或 files 配置中指定的目录)整体打包进 ASAR 归档或直接拷贝到 resources/app 下。
最常见的问题:开发时能加载图片,打包后却 404。
原因通常是你在代码里用了相对路径,比如 ./assets/logo.png,但打包后进程的工作目录变了。正确做法是永远从 __dirname 或 app.getAppPath() 动态获取资源根路径。
// 主进程或 preload 中
const path = require('path');
const { app } = require('electron');
// 获取应用根目录(打包后 ASAR 内部仍有效)
const appRoot = app.isPackaged
? path.dirname(app.getPath('exe')) // 生产环境
: app.getAppPath(); // 开发环境
// 拼接静态资源路径
const logoPath = path.join(__dirname, 'assets', 'logo.png');
如果你使用了 ASAR 打包(默认行为),__dirname 会指向 ASAR 虚拟文件系统,小文件读取完全正常。但如果你需要把资源放到 ASAR 外部(例如供其他程序调用的可执行文件),就必须用 extraResources 配置。
18.4.2 原生模块(.node)的打包
许多 Node.js 原生模块(如 better-sqlite3、sharp、robotjs)需要针对不同平台编译。打包时的处理策略很简单:
- 在
package.json的build中确保node_modules被打包(默认已包含)。 - 使用
electron-rebuild或@electron/rebuild重新编译原生模块,使其适配 Electron 的 Node.js 版本。 - 如果模块依赖了系统级动态库(如
libvips、ffmpeg),需要将其列为extraResources或使用专用打包插件(例如sharp提供了预编译二进制)。
# 开发时安装并重新编译
npm install better-sqlite3
npx @electron/rebuild
在 electron-builder 配置里,通常无需额外干预,除非模块的二进制文件没被自动打进 ASAR。如果遇到 “Cannot find module xxx.node” 错误,检查是否因为 ASAR 解压不完整。可以尝试临时禁用 ASAR 来定位问题:
// package.json
"build": {
"asar": false
}
如果确认禁用 ASAR 后正常,可能是模块的加载路径有特殊处理。此时可将该模块的 node_modules 整体外置于 app.asar.unpacked,使用 asarUnpack 配置:
"build": {
"asar": true,
"asarUnpack": [
"node_modules/better-sqlite3/**"
]
}
18.4.3 外部可执行文件、动态库等原生资源
当你的应用需要调用第三方可执行程序(如 ffmpeg.exe、python 脚本)或者依赖外部 .dll / .dylib 时,这些资源必须真实存在于文件系统中,不能被打包进 ASAR(因为操作系统无法从 ASAR 虚拟磁盘执行二进制文件)。
正确的做法是使用 extraResources 字段将它们拷贝到安装目录的 resources 文件夹下。
// package.json 的 build 配置
"build": {
"extraResources": [
{
"from": "bin/${os}/",
"to": "bin",
"filter": ["**/*"]
}
]
}
上面的配置会从项目根目录的 bin/win/、bin/mac/ 或 bin/linux/ 拷贝对应平台的可执行文件到最终应用的 resources/bin 目录。
在主进程中获取这些资源的路径,可以使用 process.resourcesPath:
const path = require('path');
const { app } = require('electron');
// 打包后 resources 的绝对路径(ASAR 外)
const resourcesPath = process.resourcesPath;
const ffmpegPath = path.join(resourcesPath, 'bin', 'ffmpeg');
// 调用
const cp = require('child_process');
cp.execFile(ffmpegPath, ['-version'], (err, stdout) => {
console.log(stdout);
});
提示:extraResources 中的文件会拷贝到 resources/ 而不是 resources/app/,路径一定不要搞错。
18.4.4 大体积资源的分包策略
如果你的应用包含几百 MB 的模型文件或视频素材,全塞进 ASAR 会导致安装包巨大且启动加载缓慢。这时可以利用 extraResources 将这些大文件外置,实现“基础包 + 外部资源”的结构。对于需要按需下载的静态资源,更是建议直接从服务器拉取,而非内置在安装包中。
18.4.5 常见问题速查
| 现象 | 原因 | 解决 |
|------|------|------|
| 图片/字体加载 404 | 使用了相对路径 | 改用 __dirname 或 app.getAppPath() 拼接绝对路径 |
| .node 模块报 “not a valid Win32 application” | 原生模块未针对 Electron 重新编译 | 运行 npx @electron/rebuild |
| 调外部 exe 提示 “找不到文件” | exe 被包进 ASAR | 用 extraResources 外置,通过 process.resourcesPath 定位 |
| 打包后文件路径在开发环境不一致 | process.resourcesPath 在开发时指向不同目录 | 用 app.isPackaged 判断,开发时指向项目根目录 |
| ASAR 文件过大 | 所有资源和 node_modules 都被压缩 | 用 asarUnpack 将大模块或资源外置 |
将上述配置和自己的工程目录对齐,你的静态资源与原生资源就能在两个阶段(开发、生产)都正常工作,不再出现“开发环境一切正常,打包后各种崩溃”的扎心场景。