<!-- title: JS臃肿的三大支柱：npm依赖地狱的系统性拆解 -->
<!-- domain: programming -->
<!-- video: https://www.youtube.com/watch?v=1t-k6-m50Fc -->

# JS臃肿的三大支柱：npm依赖地狱的系统性拆解

## 导语

2026年3月，Theo Browne（t3.gg）发布了一支33分钟的视频，标题叫"Saving the web from Javascript bloat"。538K订阅的YouTuber，拍了一支关于JavaScript包管理生态的深度分析——这在"编程娱乐化"的YouTube圈子里并不常见。但视频火了。不仅被翻译到Bilibili，还引发了Hacker News上489个upvote、272条评论的激烈辩论，甚至催生了一篇由James Garbutt（43081j）撰写的系统性文章"The Three Pillars of JavaScript Bloat"，直接推动了e18e社区清理npm生态的行动。

这篇文章不是简单的视频搬运。我将基于视频的核心论点，结合e18e社区的技术框架、HTTP Archive的数据统计、以及Hacker News社区的第一手讨论，给出一份对JavaScript依赖臃肿问题的完整拆解。读完之后，你应该能回答一个问题：**为什么你的node_modules比你的项目代码还大？**

## 视频背景卡片

| 项目 | 内容 |
|------|------|
| 视频标题 | Saving the web from Javascript bloat |
| 频道 | Theo - t3.gg |
| 时长 | 约33分钟 |
| 频道订阅 | 538K+ |
| 核心主题 | JavaScript依赖膨胀的三大结构性根因及e18e清理行动 |
| 关联视频 | How JS ruined the web（同期发布） |
| 社区影响 | Hacker News 489 points/272 comments |

## 核心内容拆解

### 一、第一支柱：Legacy Compatibility——为死人写的Polyfill

Theo在视频中指出的第一个核心问题，是npm生态中大量包仍然在为已经不存在的运行时环境提供兼容性支持。

这不是夸张。你在npm上随便找一个老牌工具库，它的`package.json`里很可能写着`"engines": ">=0.10"`——Node.js 0.10发布于2013年。IE11在2022年6月已经正式停止支持。但2026年，仍有大量npm包在shipping代码去兼容这些死去的浏览器。

这背后的机制是这样的：许多包的入口文件会做运行时检测（runtime detection），然后动态加载polyfill。比如`object.assign`、`Array.prototype.includes`、`Promise`——这些在所有现代浏览器中早已原生支持。但polyfill代码依然被包含了进来，被打包进了你的最终bundle。

**数据支撑**：根据HTTP Archive的报告，2026年网页平均加载超过500KB的压缩JavaScript。其中相当一部分是这些"幽灵polyfill"——为永远不会访问你网站的浏览器准备的代码。

James Garbutt在他的文章中举了一个具体例子：许多包在`browserslist`配置中仍然包含`IE 11`，即使项目的target audience根本不需要IE支持。这导致Babel和PostCSS等工具在编译时包含了不必要的polyfill，直接增加了bundle大小。

**延伸分析（非视频原话）**：这个问题之所以顽固，是因为"兼容性"在工程文化中被过度神圣化了。谁都不想做"那个破坏兼容性的人"。但当一个包的兼容性成本由每一个下游消费者承担时，这就变成了一个公地悲剧——每个人都在为不存在的用户付钱。

### 二、第二支柱：Atomic Package Architecture——一个函数一个包的灾难

第二支柱涉及npm生态一个根深蒂固的设计哲学：原子化包（atomic packages）。

npm历史上有一个著名的案例：`is-odd`包——专门用来判断一个数是否为奇数。每周下载量一度达到数百万。还有`is-number`、`is-array`、`is-string`……这些包每个只做一件事，代码不超过10行，但它们创造了一个极其深层的依赖图（dependency graph）。

Theo在视频中展示了这种架构如何导致灾难性的后果：

1. **依赖深度爆炸**：你的项目依赖A，A依赖B，B依赖C，C依赖`is-number`。看起来无害，但当整个npm生态都这么做时，一个中等规模的项目可以轻易拉下上千个直接和间接依赖。

2. **供应链攻击面**：2025年9月，npm生态经历了史上最大的供应链攻击之一——18个包被入侵，影响26亿次周下载量。攻击者正是利用了这种深度依赖链中的薄弱环节。当你的项目间接依赖1500个包时，你无法审计其中每一个的维护者是否值得信任。

3. **版本锁定僵局**：不同层级的依赖可能要求同一个包的不同版本。当底层原子包发布breaking change时，整条依赖链都会受到冲击。left-pad事件（2016年）就是最经典的案例——一个11行的函数包被作者从npm撤下，直接导致成千上万个项目构建失败。

**Hacker News社区观点**：HN用户`auxiliarymoose`的评论获得了大量认同——"I really think writing dependency-free JavaScript is the way to go nowadays. The standard library in JS/CSS is great."（我认为写无依赖的JavaScript才是今天的正确方向。JS/CSS的标准库已经很强大了。）

### 三、第三支柱：Utility Redundancy——重新发明标准库的轮子

第三个支柱是最让人心痛的：大量npm包提供的功能，JavaScript标准库已经原生支持。

lodash是最好的例子。2026年，lodash仍然是npm上下载量最大的包之一，周下载量超过5000万。但lodash中大量函数——`_.assign`、`_.forEach`、`_.map`、`_.find`、`_.includes`、`_.flatten`——在ES2015+中都有原生等价物。即使是你以为需要lodash的那些"高级"函数，比如`_.debounce`、`_.throttle`，用几行原生代码也能实现。

Theo在视频中做了一个让人印象深刻的对比：展示了一个使用30个lodash工具函数的项目，如何通过替换为原生API将bundle大小减少了数百KB。

不仅是lodash。还有：
- **moment.js** → 已被`Intl.DateTimeFormat`和`Temporal` API替代
- **axios** → `fetch` API已是标准
- **uuid** → `crypto.randomUUID()`已内置于所有现代浏览器和Node.js
- **qs** → `URLSearchParams`已内置于Web API
- **deep-merge类包** → `structuredClone()`已原生支持

**关键洞察**：这不是开发者"不知道"的问题。这是惯性。项目模板、教程、Stack Overflow回答——整个知识体系都在推荐这些包。新项目从一个包含lodash的boilerplate开始，然后每个新加入的开发者都习惯性地`import _ from 'lodash'`，却只用了其中两三个函数。

### 四、e18e：一场清理npm生态的社区运动

视频的一个重要部分是介绍了**e18e**（Ecosystem Performance）社区。这个社区的目标可以用三个词概括：**Cleanup, Speedup, Levelup**。

**Cleanup**：识别并移除冗余依赖。e18e维护了一份"可替代包"清单，列出了可以用原生API或更现代的包替代的老旧依赖。

**Speedup**：提升广泛使用的包的性能。通过贡献PR到大型项目，减少它们的bundle大小。

**Levelup**：构建现代替代品。为那些确实有存在价值的老旧包创建更轻量、更快的现代版本。

e18e还发布了一个ESLint插件（`@e18e/eslint-plugin`），可以在开发时自动检测项目中可以替换为原生API的依赖。这把"发现问题"从被动变成了主动——你不需要专门去做dependency audit，插件会在你写代码的时候告诉你"嘿，这个lodash调用可以用原生API替代"。

**我的判断**：e18e是目前JavaScript生态中最有意义的社区项目之一。它不是在创建新框架——那只会增加复杂性。它是在做减法。在一个长期以"加法"为默认选择（加个依赖、加个抽象层、加个框架）的生态中，做减法需要更大的勇气。

### 五、Bundle Size只是症状，不是病因

Theo在视频中反复强调一个观点，也是我认为这个视频最有价值的洞察：**bundle size大只是症状，真正的病根是npm生态的结构性问题。**

这意味着：
- **Tree-shaking不是万能药**：你可以开aggressive tree-shaking，但如果你的依赖树本身就包含冗余代码，shake掉的是枝叶，不是树根。
- **Code-splitting只是拖延问题**：把一个大bundle切成几个小bundle，总下载量并没有减少。
- **CDN和缓存也不是解决方案**：它们只是掩盖了"你在下载你不需要的代码"这个事实。

真正的解决方案是**从依赖树的源头清理**——这正是e18e在做的事。

### 六、向前兼容 vs 向后兼容的思维转换

视频中一个让我印象深刻的论点是关于"向前兼容"（forward compatibility）的。

传统的思维是向后兼容——确保新代码能在旧环境运行。但这导致了第一支柱（Polyfill）的问题。

Theo提出的替代思维是：**假设环境是现代的，然后按需添加兼容性支持**。这意味着：

- 你的库的默认版本应该假设ES2022+和现代浏览器
- 如果某个消费者确实需要IE11支持，那是**他们的问题**，不是你的问题
- 使用`browserslist`和`targets`配置让每个项目自己决定兼容性边界

这种思维转换的工程意义是深远的：它把兼容性成本从"全生态分摊"变成了"谁需要谁承担"。这会让整个npm生态的默认bundle大小显著下降。

### 七、开发者行动指南：你今天就能做的五件事

视频最后给出了一些具体的行动建议：

1. **运行`npx @e18e/eslint-plugin`扫描你的项目**：它会告诉你哪些依赖可以被移除或替换
2. **检查你的`browserslist`配置**：移除不再需要支持的旧浏览器
3. **审计你的lodash/underscore使用**：用[native.es](https://github.com/nicolo-ribaudo/eslint-plugin-no-unnecessary-lodash-alias)等工具自动检测可替换的调用
4. **减少间接依赖数量**：`npx depcheck`可以找出未使用的依赖，`npm ls --depth=0`查看直接依赖
5. **选择轻量替代品**：需要工具函数？用`es-toolkit`替代lodash。需要日期处理？用`Temporal`替代moment.js

## 知识结构图

```mermaid
mindmap
  root((JavaScript依赖臃肿))
    三大支柱
      第一支柱：Legacy Compatibility
        死去的浏览器Polyfill
        过时的browserslist配置
        运行时检测开销
      第二支柱：Atomic Package Architecture
        单函数包泛滥
        依赖深度爆炸
        供应链攻击面扩大
        left-pad事件重演风险
      第三支柱：Utility Redundancy
        lodash可被原生API替代
        moment.js → Temporal
        axios → fetch API
        uuid → crypto.randomUUID
    解决方案
      e18e社区行动
        Cleanup: 移除冗余依赖
        Speedup: 优化包性能
        Levelup: 构建现代替代品
        @e18e/eslint-plugin
      思维转变
        向前兼容替代向后兼容
        兼容性成本归消费者
        从源头清理依赖树
      开发者行动
        审计browserslist
        替换冗余依赖
        减少间接依赖数量
    数据支撑
      平均页面500KB+ JS
      npm周下载量数十亿
      2025供应链攻击影响26亿次下载
```

## 实战启示

### 对个人开发者的实际帮助

这个视频最大的实用价值在于：它给你了一个**系统性的框架**来理解和解决JavaScript依赖膨胀问题，而不是给你一堆零散的"最佳实践"。

具体来说：

**如果你的项目node_modules超过200MB**：用三大支柱框架做一次诊断。大概率你会发现（1）某些依赖在兼容你不支持的浏览器，（2）依赖树中有大量单函数包，和（3）有可以用原生API替代的工具库。按这个顺序清理，效果立竿见影。

**如果你在构建npm包**：重新审视你的兼容性策略。默认假设现代环境，把polyfill的决定权交给下游消费者。这不仅减少了你的维护负担，也是对整个生态的负责。

**如果你在做技术选型**：评估一个包时，不要只看它提供了什么功能——还要看它拉下了多少依赖。一个`npm ls`命令就能让你看到一个包的真实成本。一个50KB的包如果拉了200个间接依赖，它的真实成本远不止50KB。

**对团队的影响**：把`@e18e/eslint-plugin`加入你的CI pipeline。让依赖审计变成自动化流程，而不是靠人的自觉性。在一个10人团队中，如果每个人每次PR都自觉地检查依赖是否必要，那很好——但更可靠的做法是让机器帮你盯着。

## 延伸学习路径

1. **[The Three Pillars of JavaScript Bloat](https://43081j.com/2026/03/three-pillars-of-javascript-bloat)** — James Garbutt的原始博客文章，三大支柱框架的系统性阐述，是Theo视频的核心参考来源
2. **[e18e.dev](https://e18e.dev/)** — Ecosystem Performance社区官网，包含可替代包清单、ESLint插件、以及贡献指南
3. **[HTTP Archive: Page Weight](https://httparchive.org/reports/page-weight)** — 追踪网页资源大小和数量的实时数据，理解JS bundle大小的宏观趋势
4. **[How JS ruined the web](https://www.youtube.com/watch?v=O-QS_Cp_zXg)** — Theo的同期视频，同一主题的另一个角度
5. **[Cleaning & speeding up the JS ecosystem](https://dev.to/43081j/cleaning-speeding-up-the-js-ecosystem-journey-so-far-2pe6)** — e18e社区的清理行动进展报告

## 一句话总结

**JavaScript依赖膨胀不是个别开发者的懒惰，而是npm生态三个结构性缺陷——幽灵兼容、原子化包、标准库重复——的系统性后果；修复它需要的不是更多工具，而是更少的依赖。**

## 参考来源

1. [Saving the web from Javascript bloat](https://www.youtube.com/watch?v=1t-k6-m50Fc) — Theo - t3.gg 的核心分析视频
2. [The Three Pillars of JavaScript Bloat](https://43081j.com/2026/03/three-pillars-of-javascript-bloat) — James Garbutt 的系统性文章
3. [Hacker News 讨论](https://news.ycombinator.com/item?id=47473718) — 489 points / 272 comments 的社区讨论
4. [e18e | Ecosystem Performance](https://e18e.dev/) — npm生态清理社区
5. [JavaScript Bloat in 2026: 3 Root Causes & Fixes](https://www.kunalganglani.com/blog/javascript-bloat-causes-fixes) — 实用修复指南

---

> 本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵**爱卫生**（微信ID: gprshome201101）维护，聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。
> 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
