AI 生成的摘要
文章详细介绍了一位开发者如何通过一系列优化手段,成功将其基于 Electron + Vite 开发的 AI 桌面编程助手的安装包大小从 180MB 缩减至 80MB,以及将 app.asar 文件从 416MB 减至 56MB。主要优化措施包括将仅用于渲染进程的依赖移至 devDependencies,利用 electron-builder 启用最大压缩选项,以及使用 afterPack 钩子删除不必要的文件。这些步骤帮助显著减少了应用的体积,同时保持了功能完整。作者还分享了一些经验教训,如逐步验证每一步优化效果和定期审计依赖的必要性。文章为那些遇到类似体积挑战的开发者提供了实用的指导和灵感。

本文章由 cladue opus 4.5 润色。

最近 Vibe coding 了一个 AI 桌面编程助手,基于 Electron + Vite。最近发现安装包膨胀到 180MB 了,下载体验很差,于是花了点时间做了一轮系统性优化。

最终成果:DMG 从 180MB 降到 80MB,app.asar 从 416MB 降到 56MB。没改任何业务代码。

这篇文章记录整个过程。


最有效

1. 把 renderer-only 依赖移到 devDependencies(asar -267MB)

这是整个优化里收益最大的一步,效果超过其他所有步骤加起来。

原理很简单:应用用 Vite 打包 renderer 进程,产出的是一个自包含的 bundle(20MB),运行时根本不需要 node_modules 里的东西。但 electron-builder 打包时会把 dependencies 里所有包都塞进 asar——不管你 bundle 用没用到。

所以像 @lobehub/uireactzustandlucide-react 这些只在 renderer 里用的包,全部应该放到 devDependencies

sh
npm install --save-dev @lobehub/ui react react-dom zustand lucide-react

一行命令,asar 从 323MB 直接掉到 56MB。

electron-builder 打包会把 dependencies 里的包连带整个依赖树一起打进去。这两个过程是独立的。

2. electron-builder 开启最大压缩(DMG -23MB)

一行配置的事:

json
{  
  "build": {  
    "compression": "maximum"  
  }  
}

默认是 normal,改成 maximum 后 DMG 立减 14%。代价是构建时间稍微长一点,但对发布构建来说完全可以接受。

3. afterPack 钩子清理垃圾文件(unpacked -19MB)

Electron 用 native 模块(比如 better-sqlite3)时,需要把 .node 文件从 asar 里解压出来。问题是解压的时候会把整个模块目录都带出来,包括一堆运行时根本不需要的东西:

  • sqlite3.c 源码(9MB)

  • README、LICENSE、CHANGELOG

  • .gypMakefile 等构建文件

另外 Electron 框架自带 70 多种语言的 locale 文件,每个几百 KB,应用只需要保留一种即可(参考 innei 播客)。

用 afterPack 钩子在打包后、签名前把这些都清掉:

scripts/afterPack.js
js
exports.default = async function(context) {  
  // 清理 native 模块里的源码和文档  
  const junkPatterns = ['**/*.c', '**/*.h', '**/README.md', '**/*.gyp']  
  // ... 删除匹配的文件  
​  
  // 只保留英文 locale  
  const keepLocales = ['en.lproj']  
  // ... 删除其他 locale 目录  
}

unpacked 目录从 21MB 降到 2MB,DMG 也跟着小了 12MB。


中等收益的优化

这几个不如上面三个效果明显,但也值得做:

移除未使用的依赖(asar -87MB)

全局搜一下 import 语句,发现 antd、react-markdown 这些包装了但根本没用到。Web 项目里这不是大问题(Vite 会 tree-shake 掉),但 Electron 会把整个 node_modules 打进去,所以该删还是得删。

Vite 构建配置

json
esbuild: {  
  drop: ['console', 'debugger']  // 生产环境移除调试代码  
}

renderer bundle 能小个几百 KB。


试了但没啥用的

记录一下,省得踩同样的坑:

精确配置 asarUnpack 的 glob 模式

我以为把 glob 写精确点,就能只解压 .node 文件而不带上源码:

"asarUnpack": ["**/better-sqlite3/build/Release/*.node"]

然而并没有用。electron-builder 的逻辑是:glob 匹配到哪个模块,就把整个模块目录都解压出来。想精确控制解压内容,只能用 afterPack 钩子事后清理。

静态资源压缩

把 logo.png 从 228KB 压到 26KB,icon.icns 从 1.2MB 压到 656KB。但经过 asar 打包和 DMG 压缩后,在 MB 级别的测量精度下基本看不出差异。属于代码卫生层面的事,对最终体积影响不大。

files 配置排除 .map 文件

"files": ["!out/**/*.map"]

配了,但当前构建本来就没生成这些文件,所以没有实际收益。算是防御性配置。


各步骤收益汇总

优化项DMGasarunpacked
renderer 依赖移到 devDep-49MB-267MB
最大压缩配置-23MB-12MB
afterPack 清理-12MB-19MB
移除未使用依赖-16MB-87MB
Vite 构建优化-12MB
累计-100MB-360MB-19MB

一些经验

每步都要打包验证。不要一口气做完所有优化再测,而是改一步、打包一次、记录数据。这样才能知道哪步真正有效、哪步是白忙活。我们就是靠这个方法发现 asarUnpack 优化其实没用的。

构建有波动。相同代码多次打包,asar 体积可能有 ±6-12MB 的波动,别被误导了。

Electron 的依赖管理比 Web 敏感得多。Web 应用只打包 import 的代码,Electron 会把整个 node_modules 带上。定期审计依赖是必要的。


剩下的优化空间主要在 Electron 框架本身(比如 16MB 的 Vulkan 渲染器我们其实用不到)和 renderer bundle 的懒加载(mermaid 只在渲染图表时需要)。这些后面再搞。