近年来,JavaScript 项目的 npm 依赖树正变得越来越臃肿。这主要源于三个核心问题:为了支持极少数老旧环境而引入的复杂兼容层、过度拆分代码的“原子化”倾向,以及在特性已原生化后仍未移除的过时补丁。这种现状不仅拖慢了安装速度,还增加了安全风险。开发者需要通过现代工具审视并精简依赖,将性能负担从主流用户身上剥离。
1. 为了兼容“极少数人”的过度保护
很多基础工具包(如 is-string 或 hasown)在代码中套了一层又一层。这种现象背后有三个原因:
- 支持远古引擎: 为了让代码在 IE6/7 或极早期的 Node.js(仅支持 ES3)上运行。
- 防止全局变量被篡改: 为了防止其他脚本修改了像
Map这样的全局对象,某些包会选择不直接使用原生对象,而是导入一堆复杂的保护逻辑。 - 跨域(Cross-realm)支持: 处理来自不同窗口或
<iframe>的数据,确保类型检查在极端环境下依然有效。
核心矛盾:
绝大多数现代项目运行在近十年的 Node.js 或常青浏览器中,根本不需要这些复杂的兼容方案。现在的情况是:为了极少数人的特殊需求,让所有开发者都承担了冗余成本。
2. “原子化”架构带来的碎片化
部分开发者推崇将代码拆分到极致,甚至一个变量、一行代码也要发布成一个 npm 包。
- 典型的原子包例子:
shebang-regex(仅一行正则)、arrify(简单的数组转换)、is-windows(检查系统平台)。 - 潜在风险:
- 单用途浪费: 许多包只有一个下游使用者,却增加了下载、解析和提取的开销。
- 版本冗余: 一个复杂的依赖树中可能同时存在同一个微型包的多个不同版本。
- 供应链攻击: 每一个额外的包都是一个潜在的安全漏洞。去年就曾发生过某位维护者账号被盗,导致数百个微型原子包被植入恶意代码的事件。
建议: 简单的逻辑应该直接写在代码里(Inlining),而不是为了那一两行代码去引入一个外部依赖。
3. “长命百岁”的 Ponyfill 补丁
当开发者想在旧环境使用新特性时,通常会用到 Ponyfill(不污染全局环境的特性补丁)。
- 问题所在: 许多功能(如
Object.entries、globalThis)早在多年前就已成为原生标准,被各大引擎广泛支持。 - 现状: 许多库依然依赖于这些补丁包,仅仅是因为没人去清理它们。这些包每周仍有数千万次的下载量,成了依赖树中挥之不去的“幽灵”。
我们该如何应对?
要解决这种深层嵌套的冗余,需要社区共同努力。你可以采取以下行动:
- 自问“为什么”: 在引入一个包之前,问问自己:“我真的需要它吗?它的功能原生能实现吗?”
- 利用清理工具:
knip: 自动寻找项目中未使用的依赖项和死代码。e18eCLI: 专门用于分析和迁移冗余依赖,它可以告诉你哪些包可以用原生功能或更轻量的替代品。
- 寻找替代品: 参考 module-replacements 项目,将臃肿的旧包替换为更现代、更纯净的选择。
- 可视化分析: 使用 npmgraph 查看你的依赖树,找出那些深藏其中的冗余分支。
我们应该扭转局面:让那些需要特殊兼容性的极少数用户去寻找特定方案,而让大多数人享受到现代化、轻量化且原生的开发体验。