Electron 应用被诟病最多的一点就是体积——即便是最简单的 Hello World,打包后也会轻松突破 100 MB。这是因为每个 Electron 应用都内嵌了一个完整的 Chromium 和 Node.js 运行时。但除此之外,项目自身的 node_modules 也是体积膨胀的重灾区。这一节不会去争论“体积是否真的重要”,而是直接给出几种经过验证的瘦身方案,让你的应用在分发给用户时尽可能精简。
18.5.1 理解体积的来源
在动手优化之前,先搞清楚一个 Electron 应用的安装包体积通常由哪几部分组成:
- Electron 运行时二进制:大约 50~70 MB(压缩后),这是硬性开销,几乎无法削减。
- Chromium 的资源文件:例如
locales(多语言包)、swiftshader(软件渲染库)等,大约 10~20 MB,可以通过配置剔除不用的部分。 - 应用自己的代码:业务逻辑、前端资源、静态文件,通常几兆到十几兆。
node_modules中的依赖:这是体积差异最大的部分。一个简单的原生模块(如sqlite3)可能只带几十 KB 的二进制文件,但如果引入了分析工具、PDF 生成库或图像处理库,体积可能会暴涨到几百 MB。
优化重点很明确:在保证功能的前提下,尽可能减少第 2 和第 4 部分的体积。
18.5.2 剔除不必要的 Electron 文件
使用 electron-builder 打包时,默认会将整个 Electron 分发版复制进去,包含很多你实际上用不到的文件。可以通过配置文件裁剪掉它们:
# electron-builder.yml
win:
target:
- target: nsis
arch: [x64]
extraResources: [] # 如果没有额外资源,可以不填
mac:
target:
- target: dmg
arch: [x64, arm64]
# 剔除掉非必要的 Chromium 资源
files:
- '!node_modules/**/*'
# 只保留必要的语言包(中英文)
- '!node_modules/electron/dist/locales/*'
- 'node_modules/electron/dist/locales/en-US.pak'
- 'node_modules/electron/dist/locales/zh-CN.pak'
# 如果需要,也可以删除掉 swiftshader 等
你甚至可以写一个自定义的 afterPack 钩子脚本来进一步删除不需要的文件,比如 swiftshader、libEGL、libGLESv2 等(如果应用不依赖 WebGL 之外的图形处理)。
18.5.3 node_modules 的公共依赖抽离
很多中大型 Electron 项目最终会打包出多个安装包(Windows × macOS × Linux),如果每个平台都独立压缩一次全量的 node_modules,在 CI 构建时间和存储空间上都是浪费。更重要的是,有些非常大的依赖(比如 puppeteer 的自带 Chromium、sharp 的预编译二进制)如果被直接打入 asar 包,会让你的应用安装包体积翻倍。
可以采用以下几种策略来抽离和控制这些依赖的体积。
1. 使用 node_modules 扁平化与 dependencies 精细化
在 Electron 项目中,务必区分 dependencies 和 devDependencies。只有 dependencies 中的包会被打包进最终产品,而构建工具(webpack、vite)、测试框架、类型声明都应该放在 devDependencies 中。这是一个基本但常常被忽视的细节。
进一步,可以对一些大体积的依赖进行精细控制:
- 如果用了
puppeteer,在打包时应该指定环境变量让它在运行时使用系统已安装的 Chrome 或 Electron 自带的 Chromium,而不是额外下载一个完整的 Chromium。例如在安装依赖时设置PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true,然后在代码中通过puppeteer.launch({ executablePath: require('electron').app.getPath('exe') })来复用。 - 对于
sharp这类图像处理原生模块,可以利用它提供的sharp.version选项,只保留当前平台需要的二进制(electron-builder可以配合postinstall脚本自动移除其他平台的二进制文件)。
2. 共享原生依赖:使用 nodeModules 共享路径
如果你需要打包多个 Electron 应用(例如一个主程序加上多个子程序),或者同一应用的不同版本需要共享依赖,可以通过配置 electron-builder 将 node_modules 放置在用户系统的公共目录而不是应用安装目录内。这大大减少了每个应用的体积,并且更新依赖时可以独立于应用版本。
# electron-builder.yml
directories:
# 将 node_modules 输出到应用的资源目录之外
output: dist
buildResources: build
# 使用额外的资源来引用外部 node_modules
extraResources:
- from: node_modules
to: ../shared_modules
然后在主进程启动时手动将 ../shared_modules 添加到 NODE_PATH,保证模块能被正常 require:
// main.js
const path = require('path');
process.env.NODE_PATH = process.env.NODE_PATH
? process.env.NODE_PATH + path.delimiter + path.join(__dirname, '..', 'shared_modules')
: path.join(__dirname, '..', 'shared_modules');
require('module')._initPaths();
这种方式适合同时分发多个相关应用的场景,比如一个系统托盘工具 + 设置面板的组合。
3. 借助 Webpack / Vite 进行 Tree Shaking 和 Code Splitting
现代前端打包工具在分析 require 或 import 时具备强大的 tree shaking 能力,可以只把用到的模块打包进最终产物,而不是整个 node_modules。对于 Electron 的主进程和渲染进程,都可以使用打包工具来减少体积:
- 渲染进程:常规的 Vue/React 项目已经这样做,不必多言。
- 主进程:可以用
webpack或esbuild把主进程的 JavaScript 打包成一个单独的 bundle,并排除不需要的原生模块。
// webpack.config.js (主进程打包示例)
const path = require('path');
const nodeExternals = require('webpack-node-externals');
module.exports = {
entry: './src/main/index.js',
target: 'node', // 把主进程当作 Node 环境打包
externals: [
// 排除 Electron 内置模块,避免重复打包
nodeExternals({
allowlist: [], // 需要打包进输出的模块
}),
],
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.bundle.js',
},
// ...
};
打包后,你可以直接让 Electron 加载 main.bundle.js,node_modules 中那些没有被实际使用的代码就不会再出现在应用里。
4. ASAR 压缩与拆包
默认情况下,electron-builder 会将应用的资源(包括 node_modules)打包成一个 app.asar 文件。这个 asar 归档本身不压缩,但可以通过启用压缩来减小体积:
asar: true
asarOptions:
compression: 'maximum' # 使用最大级别的 zlib 压缩
不过要注意,压缩后的 asar 会影响首次加载的性能,因为解压需要 CPU 时间。如果你的 node_modules 特别大(超过 200 MB),可以考虑将大的原生模块排除在 asar 之外,让它们以普通文件形式存在:
asar: true
asarOptions:
unpack: '**/node_modules/big-library/**'
这样 big-library 不会被锁在 asar 里,Node.js 可以直接从磁盘读取它,既避免了压缩/解压开销,也防止了 asar 体积过于庞大。
18.5.4 安装包层面的优化
体积控制还需要关注最终生成的安装包(.exe/.dmg/.deb)大小。有几个实用技巧:
- NSIS 安装包压缩(Windows):
electron-builder为 NSIS 安装器内置了 LZMA 算法,但你可以显式配置为solid压缩,进一步减少体积(以牺牲安装速度为代价)。
nsis:
oneClick: false
allowToChangeInstallationDirectory: true
differentialPackage: false
installerIcon: icon.ico
uninstallerIcon: icon.ico
# 使用 solid 压缩可节省 10%~20%
solid: true
- macOS DMG 精简:对于 macOS,可以考虑使用
mac.zip或pkg目标,有时候比 dmg 小。或者干脆使用tar.gz来分发,由用户自行拽入 Applications 文件夹。
- 增量更新:如果用户已经安装了旧版本,可以通过
electron-updater推送增量更新(delta 包),而不是每次下载完整的安装包。虽然这不改变首次安装的体积,但能极大减轻后续更新的流量压力。
18.5.5 持续监控体积
最后,体积控制不是一次性的工作,而应该成为 CI 流程的一部分。你可以在 CI 脚本中加入一个简单的检查步骤:打包完成后测量生成的安装文件大小,并设置阈值告警。例如:
SIZE=$(stat -c%s "dist/YourApp-1.0.0.exe")
if [ $SIZE -gt 157286400 ]; then # 150 MB
echo "Error: Installer size exceeds 150 MB"
exit 1
fi
这样就能在体积突然膨胀时立即发现,通常是某个依赖升级后引入了不必要的文件。
总的来说,公共依赖抽离与包体积控制的核心思路是“能不带的就不带,能共享的就共享,能压缩的就压缩”。这些方法虽然不能把 Electron 应用变成几兆的小工具,但可以让你的产品在分发和用户体验上更加轻盈。对于团队而言,每一兆的体积节省都意味着更快的下载速度、更低的带宽成本和更好的用户第一印象。