01|如果说前四篇讲的是“模块制度怎么一路分裂、补位、协调”,那最后一篇要讲的就是:为什么标准终于来了,战争却没有立刻结束
写到第五篇,模块化江湖已经走过了很长一段弯路。
第一篇讲的是,JavaScript 最早没有原生模块制度,只能靠全局变量、命名空间、IIFE 和 module pattern 这些办法硬撑。
第二篇讲的是,CommonJS 先在 Node.js 世界把模块化做成了真正可执行的制度。
第三篇讲的是,浏览器没有照抄 CommonJS,于是 AMD / RequireJS 按自己的现实长出了另一套制度。
第四篇讲的是,当多套现实谁都没法统一全场时,bundler 开始先替整个生态执行统一。
到这里,问题终于会变成一句很自然的话:
那官方标准呢?
难道 JavaScript 模块制度,真的就一直靠这些社区现实拼下去吗?
答案当然不是。
ES Modules 最终还是来了。
而且它的胜利不是小胜。
它赢下的是:
- 语言标准地位
- 浏览器原生入口
- 长期演化方向
- 现代 JavaScript 的公共合法写法
可第五篇真正要讲的,也恰恰是另一面:
ESM 赢了,但它赢下的首先是标准地位,而不是“一夜之间把旧世界全部抹平”。
这就是最后一篇最重要的起点。
02|ES Modules 真正重要,不只是因为它终于进了标准,而是因为 JavaScript 第一次拥有了官方认可的统一模块语言
这点一定要先立住。
因为如果你只从今天回头看,会很容易低估这一步的分量。
import / export 看起来太自然了。
自然到我们几乎忘了:
JavaScript 曾经用了将近二十年,才正式把模块制度写进语言标准本身。
ECMAScript 2015 明确把 modules 放进了语言目标里,明确说大型 ECMAScript 程序可以通过 modules 被拆成多个序列,并通过显式声明导入和导出关系来组织。
这不是小事。
因为这意味着,JavaScript 第一次不再需要靠:
- 社区补丁
- 用户态约定
- 运行时私货
- 工具链兜底
来证明“模块化”是合理需求。
从这里开始,模块不再只是某个环境的选择题。
它开始变成:
JavaScript 这门语言自己承认的公共结构。
这是 ESM 最硬的一场胜利。
不是它长得最漂亮。
而是它终于拥有了“官方合法性”。
03|可 ES Modules 之所以能赢,也不是因为它横空出世,而是因为前面的 CommonJS、AMD、bundler 已经把现实问题都替它踩过一遍了
这一层特别重要。
因为如果把 ESM 写成“标准委员会终于英明地发明了正确答案”,那就又把历史写扁了。
更接近真实的说法应该是:
ESM 的胜利,很大程度上建立在前面所有混乱现实已经把问题踩到足够清楚。
CommonJS 先证明了什么?
它证明模块制度必须有:
- 文件边界
- 依赖声明
- 导出接口
- 运行时装配
AMD 先证明了什么?
它证明浏览器环境不能假装没有:
- 网络加载
- 异步执行
- 页面性能
bundler 又先证明了什么?
它证明多种模块现实已经可以被静态分析、依赖图、chunk 切分和构建产物收拢成一个可部署系统。
也就是说,等 ESM 真正进标准时,很多根本问题其实已经不再模糊。
社区早就用十几年的代价,把这些问题先替标准层做成了公开题目。
所以 ESM 不是从真空里降临。
它更像:
在一整轮社区现实、工具链现实和运行时现实互相拉扯之后,标准层终于迟到地把公共答案写了出来。
04|它为什么会显得“终于像终局答案”?因为它第一次同时满足了标准合法性、静态结构和浏览器原生入口这三件事
这就是 ESM 真正强的地方。
前面的方案都各自很强,但也各自有缺口:
CommonJS强在运行时制度,但不天然适合浏览器AMD强在浏览器加载现实,但不是语言标准- bundler 强在协调现实,但它本质上还是工具链权力
而 ES Modules 第一次把几件之前拆开的东西压到了一起:
它是标准的一部分
不是社区约定,不是某个运行时的私有 API。它是静态结构
import/export不是执行时才临时猜出来的关系,这让分析和优化第一次有了语言级抓手。它有浏览器原生入口
<script type="module">让“浏览器自己认识模块”这件事终于成立了。
这三件事一旦叠在一起,ESM 的地位就和前面所有路线都不一样了。
它不再只是“某个阵营的方案”。
它开始像:
JavaScript 模块制度终于有了一个公共宪法文本。
这就是为什么最后真正赢的是它。
不是因为别的路线毫无价值。
而是因为只有它同时占住了:
- 官方性
- 长期性
- 原生性
05|可它赢下标准地位,不等于立刻赢下运行时现实,因为现实世界早就被 CommonJS 和 bundler 深深改写了
这就是第五篇必须不断提醒读者的地方。
很多技术史里,标准胜利往往容易被写成一种“此后天下太平”的时刻。
模块化不是这样。
因为当 ESM 真正进入标准并逐步进入浏览器时,现实世界已经不是白纸了。
那时候已经存在:
- 巨量
CommonJS包 npm生态的既有分发习惯- bundler 主导的工程化工作流
- 大量基于旧格式的历史项目
- Node 自己已经长成一整个
CommonJS世界
也就是说,ESM 来的时候,面对的不是一个“等待被拯救的空地”。
它面对的是一个已经非常繁忙、而且已经建好很多房子的旧城。
这就决定了它不可能一夜清场。
所以第五篇最重要的历史判断之一就是:
ESM 赢的是宪法层面的合法性;而现实世界里的迁移、兼容、解释权再分配,还要继续打很多年。
06|这也是为什么“浏览器支持了模块”并不等于 bundler 立刻退场:原生入口解决了合法性,不自动解决交付复杂度
这点必须讲清楚。
因为很多人第一次接触 ESM 时,会自然觉得:
既然浏览器都支持 <script type="module"> 了,
那是不是 bundler 从此就该退休了?
现实当然没这么简单。
MDN 自己也讲得很明白:
现代浏览器原生支持模块,是好事。
但这并不自动淘汰 bundlers。
为什么?
因为浏览器原生模块解决的,主要是:
- 模块语法合法
- 浏览器懂得按模块方式加载和执行
可应用交付里的很多难题还在:
- 产物切分
- 依赖合并
- 性能优化
- 多资源处理
- 向旧环境兼容
换句话说,ESM 带来的是“模块终于原生存在”。
可 bundler 管的从来不只是在不在。
它还管:
怎样把一个真实项目交付得更快、更小、更稳。
所以 ESM 的到来,并没有废掉 bundler。
它只是改变了 bundler 的工作重心。
从“替分裂制度擦屁股”,慢慢转向“替标准制度做现实优化”。
07|而 Node.js 对 ESM 的接入之所以拖得久、讲究又多,恰恰说明 CommonJS 世界不是说放下就能放下
这一层,是最后一篇最值得写疼的地方。
因为浏览器这边支持模块,只是故事的一半。
另一半在 Node。
Node 官方后来明确说:
ECMAScript modules是官方标准格式- Node 同时支持
CommonJS和ESM - 可以通过
.mjs、package.json里的"type": "module"等方式启用 - 还要处理它们之间的互操作
这套设计本身就已经说明问题了。
如果 ESM 真的是一到来就能替掉旧世界,那 Node 根本不需要这么小心地区分:
.cjs.mjs"type": "module""type": "commonjs"
这些东西为什么会存在?
因为 CommonJS 不是边角遗留。
它是 Node 世界长期存在的基础设施现实。
Node 不可能像没事一样说:
“从今天起大家全换。”
所以它只能走一条非常典型的 JavaScript 路:
标准前进,但旧世界继续背着走。
这条路不好看。
可它特别真实。
也正因为如此,模块战争的最后阶段,看起来常常不像胜利庆典。
更像漫长的和平谈判。
08|所以 ESM 真正赢下的,是“今后应该怎么写”的解释权;而没能立刻结束的,是“眼下已经怎么活”的旧账
把这层说清楚,整套《模块化江湖》才算真正收住。
因为它几乎可以概括整个后半段历史:
ESM 赢下的是什么?
- 官方标准地位
- 新项目的首选方向
- 静态分析和 tree-shaking 的天然优势
- 浏览器与 Node 的共同长期语言
但它没能立刻抹掉的是什么?
CommonJS生态- 历史包发布方式
- bundler 主导的工作流
- 双格式共存
- 迁移与互操作成本
也就是说,ESM 最终赢下的,不是“所有代码今天都变成它”。
而是:
“以后公共答案应该往哪边写”这件事,终于基本不再有悬念了。
这是非常大的胜利。
但它的胜利方式,不是摧毁旧世界。
而是逐步让旧世界退到次要位置。
这就是 JavaScript 模块化历史最后非常典型的一笔:
不是一刀切,而是长时间并存后,解释权慢慢完成转移。
09|为什么 ES Modules 赢了方向,却没有一次性抹平所有现实
把前面这些线叠一起看,第五篇最重要的判断可以压成一句:
ES Modules 赢了,但它赢下的是方向、合法性和未来,不是“世界从此只有一种现实”。
这里面每个词都很重要。
它赢下了方向。
今天再谈现代 JavaScript 模块制度,默认答案已经是 ESM。
它赢下了合法性。
模块不再需要靠社区临时协议来证明自己合理。
它赢下了未来。
浏览器、Node、工具链、库生态,长期都在往它靠。
可它没有赢成“一夜清场”的样子。
因为 JavaScript 的世界不是新大陆。
它是旧城改造。
而旧城改造最难的,从来不是设计蓝图。
是:
设计蓝图已经定了以后,怎么让所有还活着的人和房子继续搬过去。
这就是最后一篇真正的收束点。
10|ESM 赢下的是合法性,不是从此只剩一种现实
模块化江湖的最后一篇,最值得记住的,不是“ESM 终于打败了 CommonJS”这种过度简化的胜负叙事。
更值得记住的是:
ESM 真正赢下的,是 JavaScript 模块制度的公共合法性、长期方向和官方语言地位。
它让模块第一次同时拥有:
- 标准合法性
- 浏览器原生入口
- 静态结构优势
- 在 Node 中逐步推进的共同未来
可与此同时,它也没法抹掉:
- 旧的包生态
- 旧的运行时假设
- 旧的构建工作流
- 旧的兼容账本
所以最后一篇如果只记一句,就记这句:
ES Modules 赢了,但它赢下的不是“一夜之间消灭所有旧世界”,而是让 JavaScript 模块制度终于拥有了一个全行业都知道该往哪里走的公共未来。
编者注(事实核对):文中关于 ES Modules 作为标准的一部分,主要依据 ECMA-262 第六版 / ECMAScript 2015 规范,其中明确将 modules 作为支持大型程序的重要语言机制。关于浏览器原生模块支持与 <script type="module">,主要依据 MDN 对 JavaScript modules 和 script type 属性的说明。关于 Node.js 对 ESM 的接入、.mjs、"type": "module"、以及与 CommonJS 的互操作,主要依据 Node 官方 ECMAScript modules 与 Packages 文档。正文将 ESM 概括为“赢下标准地位但战争未立刻结束”,属于基于其标准胜利与长期兼容现实并存这一历史位置做出的总结。
关键人物速览
- Allen Wirfs-Brock:
ECMAScript 2015时代的重要项目编辑人物之一。理解为什么模块最终会进入标准正文,绕不开他。 - James Burke:虽然
AMD没有成为终局,但浏览器模块现实能被标准认真对待,他这条线功不可没。 - Node 核心团队:理解为什么
ESM在 Node 里的接入会如此漫长、谨慎而充满互操作设计,绕不开这一整条集体演化线。
参考与延伸阅读
ECMAScript 2015 Language Specification - ECMA-262 6th Edition
https://262.ecma-international.org/6.0/MDN: JavaScript modules
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/ModulesMDN: script type attribute
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/typeNode.js Docs: ECMAScript modules
https://nodejs.org/api/esm.htmlNode.js Docs: Packages
https://nodejs.org/api/packages.html
这一支主线至此收束。若继续自然外溢,最顺的下一条线就是:npm、前端工程化,或者 TypeScript。