Electron 应用的安装包体积很大一部分来自 node_modules。一个典型的项目可能有几百甚至上千个依赖,其中不少并未在运行时真正使用,或者可以用更轻量的方案替代。这一节会从“剔除无用依赖”和“替换体积过大的依赖”两个维度,给出一些能在实际项目中立刻落地的优化手段。
22.2.1 无用依赖的精准剔除
Node.js 项目很容易积累“僵尸依赖”:装了但没用到,或者曾经用过但代码已删除却仍保留在 package.json 中的包。这些包不仅增加安装体积,还可能引入安全隐患。
1. 借助工具自动检测未使用的依赖
推荐使用 depcheck 快速扫描项目:
npx depcheck
它会分析你的代码文件,找出哪些 dependencies 中的包没有被 require 或 import,以及哪些 devDependencies 没有在脚本或配置中被引用。注意 depcheck 不能 100% 准确识别像 electron-builder 这类在配置文件中静态引用的包,所以人工复核仍是必要的。
2. 区分 dependencies 与 devDependencies
在 Electron 项目中,只有主进程和预加载脚本中真正使用的包需要放入 dependencies;渲染进程的依赖如果经过打包工具(Webpack、Vite)处理,理论上所有依赖都可以放在 devDependencies,因为它们会被打包进输出文件中,不会以独立模块形式出现在最终的 app 里。
很多团队为了方便,会把所有东西都扔进 dependencies,这会导致最终安装包中保留大量只用于构建阶段的工具(如 babel 插件、测试框架)。合理划分能直接减少打包进 app.asar 的模块数量。
3. 清理 npm 的重复安装
运行 npm dedupe (或 yarn dedupe) 可以尝试将依赖树扁平化,减少重复安装相同包的不同版本。如果一个项目中同时依赖了 lodash@4.17.21 和 lodash@4.17.20,两个版本都会被安装,这无疑是浪费。dedupe 通过提升依赖来合并它们。
4. 审视安装时被拉入的间接依赖
有些直接依赖本身很轻量,却会引入大量庞大的子依赖。可以使用 npm ls 或 npx cost-of-modules 查看每个包占用的磁盘大小,识别出体积异常大的包。例如,某些云服务 SDK 可能把签名、HTTP 客户端、XML 解析器等全部打包进来,但在桌面应用中我们可能只需要其中一个核心函数。这种情况下就值得考虑替换。
22.2.2 依赖替换优化:用小包代替大包
很多功能都有更轻量级的实现,有意识地替换依赖库可以显著降低依赖树体积。
1. Lodash → 原生方法或按需引入
Lodash 在早期极大提升了 JS 开发的便捷性,但现在大部分方法已有原生替代。比如:
_.get→ 可选链?._.map/_.filter→ 原生数组方法_.debounce→ 可从lodash/debounce单独引入,或使用just-debounce-it这类仅 200 字节的小包
如果项目只用到 Lodash 的个别函数,务必使用子路径导入(import debounce from 'lodash/debounce')而非整体引入,以避免打包时包含整个库。更彻底的做法是使用 lodash-es 配合 Tree Shaking,或者直接用部分功能的 micro-lib 替代。
2. Moment.js → Day.js / date-fns
Moment.js 体积庞大(压缩后 > 70KB),且已进入维护状态。Day.js 拥有与 Moment.js 几乎相同的 API,但体积仅 2KB,还支持插件按需加载。一般替换步骤:
npm remove moment
npm install dayjs
代码改动通常只需要全局替换 moment 为 dayjs,并根据需要引入对应的插件(如 duration、relativeTime)。对于更复杂的时间处理场景,date-fns 的按需导入也非常灵活,体积控制得很好。
3. jQuery → 原生 DOM 操作或前端框架
在 Electron 的渲染进程中(通常已使用 Vue/React 等框架),jQuery 完全是多余的。如果仍然遗留了少量 jQuery 用法,建议用原生 querySelector、classList 或框架的数据驱动方式取代。实在需要快速操作 DOM 又可以选用 zepto,但最推荐的方式还是消除这一层冗余。
4. AWS SDK / 云服务全量 SDK → 独立客户端或 REST 调用
如果你的应用只用到某个云服务的单一功能(例如上传文件到 S3),引入完整 AWS SDK 会显著增大安装包。现在 AWS 提供了模块化的 SDK v3,可以只安装所需的 client:
npm install @aws-sdk/client-s3
相比 aws-sdk 整体包,体积可以缩小一个数量级。类似情况也适用于阿里云、腾讯云等 SDK。更激进的方案是直接封装对应的 REST API 调用,仅引入 axios 或 node-fetch(或直接使用 Node 18 内置的 fetch)。
5. 图片处理:用 sharp 替代 imagemagick/spawn 方案
sharp 是一个高性能的 Node.js 图片处理库,基于 libvips,安装体积比 spawn 一个 ImageMagick 进程的方案小得多,而且速度更快。如果应用中需要裁剪、缩放、格式转换等功能,选择 sharp 可以同时提升性能和减小依赖体积。
6. 大量工具函数 → 自研或内联
npm 中有许多“微小”的工具包,每个可能只有几十行代码。例如判断一个值是不是数字的 is-number、获取数组最后一元素的 arr-last 等。单独看体积不大,但安装时它们会带来各自的依赖树,以及 npm 权限文件、文档等负担。团队可以考虑把这些小函数统一维护成一个内部 utils.js 或 helpers.js 文件,既减少了依赖数量,也更利于代码评审和定制。
22.2.3 在构建阶段修剪依赖
除了源代码层面的剔除和替换,还可以在打包阶段进一步过滤掉不需要的文件:
- electron-builder 的
files配置:明确指定需要打包到 asar 中的文件或目录,排除.md、测试文件、源代码 map 等。 - 使用
.npmignore或package.json的files字段:限制发布到 npm 的文件,间接影响依赖自身被拖入的杂物。 - 利用 webpack 的
nodeExternals精确控制:在主进程打包时(如果使用了 webpack 打包主进程),通过配置 externals 将某些原生模块或大型依赖排除在 asar 之外,按需使用native-ext-loader等方式处理。不过这种方式需要小心原生模块的打包路径问题。
22.2.4 真实案例:依赖精简带来的体积变化
以一个实际的企业级即时通讯应用(类似 Slack 的客户端)为例,在未优化前 node_modules 在安装包中占用约 45MB。经过以下步骤:
- 使用
depcheck清理掉 8 个未使用依赖。 - 将
moment替换为dayjs。 - 将云服务 SDK 从全量包替换为模块化客户端。
- 移除所有 jQuery 引用。
- 合并十几个微型工具函数到项目的
utils模块。 - 修正
dependencies与devDependencies的归属。
优化后 node_modules 大小降到约 22MB,安装包整体从 190MB 减至 165MB。对于需要频繁自动更新的桌面应用来说,这 25MB 的减少直接意味着用户下载更新时节省了流量和时间。
依赖精简不是一次性工程,应当作为代码审查和 CI 的一部分持续关注。每次引入新的外部包时,问自己一句:“这个功能我是不是可以用已有的依赖实现,或者用几行代码自己写?”。这种思考习惯带来的长期收益,要远大于一次性的暴力清理。