首页 › programming › JS臃肿的三大支柱:npm依赖地狱的系统性拆解

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提出的替代思维是:假设环境是现代的,然后按需添加兼容性支持。这意味着:

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

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

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

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

知识结构图

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 — James Garbutt的原始博客文章,三大支柱框架的系统性阐述,是Theo视频的核心参考来源
  2. e18e.dev — Ecosystem Performance社区官网,包含可替代包清单、ESLint插件、以及贡献指南
  3. HTTP Archive: Page Weight — 追踪网页资源大小和数量的实时数据,理解JS bundle大小的宏观趋势
  4. How JS ruined the web — Theo的同期视频,同一主题的另一个角度
  5. Cleaning & speeding up the JS ecosystem — e18e社区的清理行动进展报告

一句话总结

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

参考来源

  1. Saving the web from Javascript bloat — Theo - t3.gg 的核心分析视频
  2. The Three Pillars of JavaScript Bloat — James Garbutt 的系统性文章
  3. Hacker News 讨论 — 489 points / 272 comments 的社区讨论
  4. e18e | Ecosystem Performance — npm生态清理社区
  5. JavaScript Bloat in 2026: 3 Root 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学通信知识星球海报-扫码加入