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 |
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大小。
延伸分析(非视频原话):这个问题之所以顽固,是因为"兼容性"在工程文化中被过度神圣化了。谁都不想做"那个破坏兼容性的人"。但当一个包的兼容性成本由每一个下游消费者承担时,这就变成了一个公地悲剧——每个人都在为不存在的用户付钱。
第二支柱涉及npm生态一个根深蒂固的设计哲学:原子化包(atomic packages)。
npm历史上有一个著名的案例:is-odd包——专门用来判断一个数是否为奇数。每周下载量一度达到数百万。还有is-number、is-array、is-string……这些包每个只做一件事,代码不超过10行,但它们创造了一个极其深层的依赖图(dependency graph)。
Theo在视频中展示了这种架构如何导致灾难性的后果:
依赖深度爆炸:你的项目依赖A,A依赖B,B依赖C,C依赖is-number。看起来无害,但当整个npm生态都这么做时,一个中等规模的项目可以轻易拉下上千个直接和间接依赖。
供应链攻击面:2025年9月,npm生态经历了史上最大的供应链攻击之一——18个包被入侵,影响26亿次周下载量。攻击者正是利用了这种深度依赖链中的薄弱环节。当你的项目间接依赖1500个包时,你无法审计其中每一个的维护者是否值得信任。
版本锁定僵局:不同层级的依赖可能要求同一个包的不同版本。当底层原子包发布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的标准库已经很强大了。)
第三个支柱是最让人心痛的:大量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(Ecosystem Performance)社区。这个社区的目标可以用三个词概括:Cleanup, Speedup, Levelup。
Cleanup:识别并移除冗余依赖。e18e维护了一份"可替代包"清单,列出了可以用原生API或更现代的包替代的老旧依赖。
Speedup:提升广泛使用的包的性能。通过贡献PR到大型项目,减少它们的bundle大小。
Levelup:构建现代替代品。为那些确实有存在价值的老旧包创建更轻量、更快的现代版本。
e18e还发布了一个ESLint插件(@e18e/eslint-plugin),可以在开发时自动检测项目中可以替换为原生API的依赖。这把"发现问题"从被动变成了主动——你不需要专门去做dependency audit,插件会在你写代码的时候告诉你"嘿,这个lodash调用可以用原生API替代"。
我的判断:e18e是目前JavaScript生态中最有意义的社区项目之一。它不是在创建新框架——那只会增加复杂性。它是在做减法。在一个长期以"加法"为默认选择(加个依赖、加个抽象层、加个框架)的生态中,做减法需要更大的勇气。
Theo在视频中反复强调一个观点,也是我认为这个视频最有价值的洞察:bundle size大只是症状,真正的病根是npm生态的结构性问题。
这意味着: - Tree-shaking不是万能药:你可以开aggressive tree-shaking,但如果你的依赖树本身就包含冗余代码,shake掉的是枝叶,不是树根。 - Code-splitting只是拖延问题:把一个大bundle切成几个小bundle,总下载量并没有减少。 - CDN和缓存也不是解决方案:它们只是掩盖了"你在下载你不需要的代码"这个事实。
真正的解决方案是从依赖树的源头清理——这正是e18e在做的事。
视频中一个让我印象深刻的论点是关于"向前兼容"(forward compatibility)的。
传统的思维是向后兼容——确保新代码能在旧环境运行。但这导致了第一支柱(Polyfill)的问题。
Theo提出的替代思维是:假设环境是现代的,然后按需添加兼容性支持。这意味着:
browserslist和targets配置让每个项目自己决定兼容性边界这种思维转换的工程意义是深远的:它把兼容性成本从"全生态分摊"变成了"谁需要谁承担"。这会让整个npm生态的默认bundle大小显著下降。
视频最后给出了一些具体的行动建议:
npx @e18e/eslint-plugin扫描你的项目:它会告诉你哪些依赖可以被移除或替换browserslist配置:移除不再需要支持的旧浏览器npx depcheck可以找出未使用的依赖,npm ls --depth=0查看直接依赖es-toolkit替代lodash。需要日期处理?用Temporal替代moment.jsmindmap
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都自觉地检查依赖是否必要,那很好——但更可靠的做法是让机器帮你盯着。
JavaScript依赖膨胀不是个别开发者的懒惰,而是npm生态三个结构性缺陷——幽灵兼容、原子化包、标准库重复——的系统性后果;修复它需要的不是更多工具,而是更少的依赖。
本文由「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 等场景全覆盖。
