Synth Daily

JavaScript 冗余的三大根源

近年来,JavaScript 项目的 npm 依赖树正变得越来越臃肿。这主要源于三个核心问题:为了支持极少数老旧环境而引入的复杂兼容层、过度拆分代码的“原子化”倾向,以及在特性已原生化后仍未移除的过时补丁。这种现状不仅拖慢了安装速度,还增加了安全风险。开发者需要通过现代工具审视并精简依赖,将性能负担从主流用户身上剥离。

1. 为了兼容“极少数人”的过度保护

很多基础工具包(如 is-stringhasown)在代码中套了一层又一层。这种现象背后有三个原因:

  • 支持远古引擎: 为了让代码在 IE6/7 或极早期的 Node.js(仅支持 ES3)上运行。
  • 防止全局变量被篡改: 为了防止其他脚本修改了像 Map 这样的全局对象,某些包会选择不直接使用原生对象,而是导入一堆复杂的保护逻辑。
  • 跨域(Cross-realm)支持: 处理来自不同窗口或 <iframe> 的数据,确保类型检查在极端环境下依然有效。

核心矛盾:

绝大多数现代项目运行在近十年的 Node.js 或常青浏览器中,根本不需要这些复杂的兼容方案。现在的情况是:为了极少数人的特殊需求,让所有开发者都承担了冗余成本。

2. “原子化”架构带来的碎片化

部分开发者推崇将代码拆分到极致,甚至一个变量、一行代码也要发布成一个 npm 包。

  • 典型的原子包例子: shebang-regex(仅一行正则)、arrify(简单的数组转换)、is-windows(检查系统平台)。
  • 潜在风险:
    • 单用途浪费: 许多包只有一个下游使用者,却增加了下载、解析和提取的开销。
    • 版本冗余: 一个复杂的依赖树中可能同时存在同一个微型包的多个不同版本。
    • 供应链攻击: 每一个额外的包都是一个潜在的安全漏洞。去年就曾发生过某位维护者账号被盗,导致数百个微型原子包被植入恶意代码的事件。

建议: 简单的逻辑应该直接写在代码里(Inlining),而不是为了那一两行代码去引入一个外部依赖。

3. “长命百岁”的 Ponyfill 补丁

当开发者想在旧环境使用新特性时,通常会用到 Ponyfill(不污染全局环境的特性补丁)。

  • 问题所在: 许多功能(如 Object.entriesglobalThis)早在多年前就已成为原生标准,被各大引擎广泛支持。
  • 现状: 许多库依然依赖于这些补丁包,仅仅是因为没人去清理它们。这些包每周仍有数千万次的下载量,成了依赖树中挥之不去的“幽灵”。

我们该如何应对?

要解决这种深层嵌套的冗余,需要社区共同努力。你可以采取以下行动:

  • 自问“为什么”: 在引入一个包之前,问问自己:“我真的需要它吗?它的功能原生能实现吗?”
  • 利用清理工具:
    • knip 自动寻找项目中未使用的依赖项和死代码。
    • e18e CLI: 专门用于分析和迁移冗余依赖,它可以告诉你哪些包可以用原生功能或更轻量的替代品。
  • 寻找替代品: 参考 module-replacements 项目,将臃肿的旧包替换为更现代、更纯净的选择。
  • 可视化分析: 使用 npmgraph 查看你的依赖树,找出那些深藏其中的冗余分支。

我们应该扭转局面:让那些需要特殊兼容性的极少数用户去寻找特定方案,而让大多数人享受到现代化、轻量化且原生的开发体验。