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 第一次把几件之前拆开的东西压到了一起:

  1. 它是标准的一部分
    不是社区约定,不是某个运行时的私有 API。

  2. 它是静态结构
    import / export 不是执行时才临时猜出来的关系,这让分析和优化第一次有了语言级抓手。

  3. 它有浏览器原生入口
    <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 同时支持 CommonJSESM
  • 可以通过 .mjspackage.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 modulesPackages 文档。正文将 ESM 概括为“赢下标准地位但战争未立刻结束”,属于基于其标准胜利与长期兼容现实并存这一历史位置做出的总结。


关键人物速览

  • Allen Wirfs-BrockECMAScript 2015 时代的重要项目编辑人物之一。理解为什么模块最终会进入标准正文,绕不开他。
  • James Burke:虽然 AMD 没有成为终局,但浏览器模块现实能被标准认真对待,他这条线功不可没。
  • Node 核心团队:理解为什么 ESM 在 Node 里的接入会如此漫长、谨慎而充满互操作设计,绕不开这一整条集体演化线。

参考与延伸阅读

  1. ECMAScript 2015 Language Specification - ECMA-262 6th Edition
    https://262.ecma-international.org/6.0/

  2. MDN: JavaScript modules
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules

  3. MDN: script type attribute
    https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type

  4. Node.js Docs: ECMAScript modules
    https://nodejs.org/api/esm.html

  5. Node.js Docs: Packages
    https://nodejs.org/api/packages.html


这一支主线至此收束。若继续自然外溢,最顺的下一条线就是:npm、前端工程化,或者 TypeScript。