今天的前端、Node 项目,几乎都会写 import。
很多时候,我们甚至已经不太把它当回事。
可如果你退一步看,会发现这件事其实非常奇怪:
为什么一门今天连“引入另一个文件”都看起来理所当然的语言,曾经会为了模块到底该怎么组织,打出一整场十几年都没完全停火的长期战争?
为什么同样都在写 JavaScript,却会先后冒出:
- 全局变量和命名空间老办法
CommonJSAMDCMD- bundler 的私家秩序
- 最后才是标准层的
ES Modules
为什么今天明明都已经有了 ESM,大家的现实世界里却还到处留着:
requiremodule.exports- 打包产物
- 双份构建
package.json里的各种格式声明
这套 《模块化江湖》,想讲的就是这些事。
它不是“教你区分 import 和 require”的语法课。
也不是“JavaScript 模块发展史速览”的压缩时间线。
它更像一部制度史。
而且是那种特别典型的 JavaScript 制度史:
共同需求明明非常明确,可统一秩序却迟迟来不了;于是社区先各自发明活法,等标准终于赶到时,现实世界早就已经长满了自己的旧账。
你可以先把这套系列记成五幕:
- 最早的 JavaScript 没有模块,大家只能靠全局变量、脚本顺序和命名空间硬撑。
Node.js起来以后,CommonJS先在服务器和运行时世界里把秩序立住。- 浏览器这边不吃同步加载那一套,于是
AMD、RequireJS,再到中文社区里的CMD、SeaJS,都各自长出来。 - 当社区格式越长越多,bundler 开始变成临时宪法,先替大家把分裂的现实勉强收拢。
ES Modules最终进了标准,可它并不是“宣布胜利”的终点,而是和旧世界继续长期磨合的新起点。
这五幕一连起来,你就会发现,这套系列真正想讲的,不是“模块语法终于被发明出来”。
而是另一件更大的事:
JavaScript 模块化的历史,不是一个标准如何自然成熟的历史,而是一整套代码组织秩序,如何在浏览器限制、服务器现实、社区分裂、工具链补位和标准姗姗来迟之间,被一点点逼出来的历史。
这套系列讲什么
我想讲的,不只是“CommonJS、AMD、ESM 分别是什么”。
更想讲的是底下那几场反复重演的冲突:
- 为什么 JavaScript 明明早就需要模块,却长期没有语言级答案
- 为什么
CommonJS会先在 Node 世界赢下秩序,却没法直接统治浏览器 - 为什么浏览器现实会逼出
AMD/CMD这种和服务器世界并不相同的路线 - bundler 为什么会在很长时间里比标准更像真正的宪法
ES Modules为什么来得这么晚,而且来了以后还要继续和旧生态反复摩擦
换句话说,这是一套关于 JavaScript 代码组织史、运行时现实冲突、社区临时制度、以及标准最终接管过程 的系列文章。
这套系列真正要追的,不是语法,是组织权
如果你退一步看,会发现模块化之争表面上像语法之争,底下其实一直在争几件更硬的事。
01|到底是谁有资格定义“一个模块”
今天我们看到 import / export,会很自然觉得:
模块不就是把代码拆开、再按需引进来吗?
可历史上最麻烦的地方恰恰在于:
“模块”从来不只是语法块,它还涉及执行时机、依赖解析、暴露边界、文件组织、加载方式。
也就是说,大家争的不只是写法。
争的是:
- 模块是不是文件
- 依赖是不是要先声明
- 导出是值拷贝还是活绑定
- 加载是同步还是异步
- 谁来负责解析和拼装这些关系
所以模块化江湖里第一个最核心的问题就是:
到底谁有资格给 JavaScript 世界定义“一个模块”的合法形态。
02|浏览器现实,还是服务器现实
CommonJS 为什么会在 Node 世界看起来那么自然?
因为它贴着服务器和本地文件系统的现实长出来。
require() 这套直觉,在那样的环境里是顺的。
可一到浏览器,问题就变了。
浏览器不天然站在本地文件系统那边。
它要面对的是:
- 网络加载
- 依赖顺序
- 并发请求
- 首屏性能
- 跨域和调试现实
于是同样都叫“模块”,浏览器和服务器其实很长时间里根本不在同一个现实里生活。
所以这套系列会反复追问:
JavaScript 模块化,为什么长期不是“统一技术方案”之争,而是“不同运行时现实谁说了算”之争?
03|社区先活下来,还是标准先定下来
JavaScript 的很多历史都有一个老问题:
标准往往来得不够快。
模块化更是如此。
需求早就存在。
代码规模早就上来了。
工程组织早就需要更稳的秩序。
可标准层长期给不出完整答案。
这时候社区不会停着等。
它一定会先自己发明办法。
于是:
- Node 社区先有
CommonJS - 浏览器社区先有
AMD - 各地实践里再长出
CMD - 更大的工程压力又催出 bundler
等标准终于赶到时,现实世界已经不是白纸了。
所以模块化江湖里另一个最重要的问题是:
当标准长期缺位时,社区临时制度一旦先活下来,后来者要怎么接管这个世界?
04|统一秩序,还是兼容旧账
模块化最大的不幸之一是:
它不是从零开始建新城。
它总是在一堆旧房子中间修路。
所以每次看似“终于统一”的时刻,后面都拖着长长的兼容账:
- 旧包怎么处理
- 老代码怎么迁
- 双格式怎么共存
- bundler 和原生模块怎么对齐
- Node 和浏览器的解释权怎么继续磨
这也是为什么今天你明明已经活在 ESM 时代,却还会不断撞见 CJS。
因为模块制度一旦进入生态深处,就不再只是新语法能不能发布的问题。
它会变成:
整个包生态、构建链、运行时约定和开发者习惯,到底愿不愿意一起迁。
05|语言标准,还是工具链现实
模块化江湖还有一个特别典型、也特别 JavaScript 的问题:
很多时候,真正定义现实的不是标准文本,而是工具链。
标准层说的是应然。
可开发者每天真的在用什么,经常由这些东西决定:
- bundler
- transpiler
- package manager
- runtime loader
也就是说,这套系列里还会一直盯着一个问题:
JavaScript 的模块制度,到底是在规范里被定义的,还是先在工程工具里被做成既成事实的?
为什么模块化这条线特别值得单独拆出来
因为它几乎是现代前端很多“重”的总入口。
你今天觉得前端复杂,往往不是因为 import 这个词本身复杂。
而是因为它背后连着一整串制度后果:
- 包如何发布
- 代码如何拆分
- 依赖如何解析
- 构建如何发生
- 浏览器如何加载
- Node 如何解释
- 生态如何兼容新旧两套格式
很多人以为模块化只是“组织代码更优雅”。
可历史真相更像:
模块化是现代 JavaScript 世界里最早、也最深地把语言标准、运行时现实、包生态和工程工具链绑在一起的一场制度战争。
如果这一层不拆清楚,后面的:
- bundler
- Babel
- 工程化
- 包分发
- Node / 浏览器双端兼容
你都会只看到表层现象,看不到它们为什么会长成今天这样。
这套系列准备怎么写
目前我更倾向于按五篇主线来写:
没有模块的时代,JavaScript 是怎么靠全局变量硬撑的
先讲为什么这个需求会出现,以及“脚本标签时代”的根本局限。CommonJS 为什么先在 Node 世界把秩序立住
讲require/exports背后的运行时现实,以及它为什么天然偏向服务器侧。AMD / CMD 为什么会在浏览器世界各自长出来
讲异步加载、浏览器限制、RequireJS、SeaJS和不同社区的选择。bundler 为什么成了临时宪法
讲当格式分裂已经成现实,为什么Webpack这类工具会成为真正负责收拾残局的人。ES Modules 为什么赢了,却没有一夜之间结束战争
讲ESM的标准化胜利,以及它为什么还要和CommonJS、工具链和运行时长期磨合。
也就是说,这套系列想讲的,不只是“谁赢了”。
更想讲:
为什么 JavaScript 模块化最后没有靠某一方彻底碾压收场,而是靠标准、运行时和工具链一起把一场长期内战慢慢熬成了今天的秩序。
先记住这句
如果你现在只想先记住一句话,那就记这句:
JavaScript 模块化的历史,不是 import 语法的诞生史,而是一场围绕“代码到底该怎样被组织、加载、分发和解释”的长期制度战争。
而且这场战争最特别的地方还在于:
它没有一个简单干净的胜利时刻。
CommonJS 没有彻底死。
AMD 没有白来。
bundler 也没有因为 ESM 出现就立刻退场。
这就是 JavaScript 式胜利。
不是一纸标准宣布天下太平。
而是:
旧世界慢慢退,新世界慢慢接,工具链在中间长期做翻译,最后大家才勉强活进同一套现实。
先记住:模块化之争从来不只是语法之争
所以这套 《模块化江湖》 真正想讲的,不是“模块化让代码更清爽”。
更想讲的是:
为什么 JavaScript 这样一门最初连模块制度都没有的语言,最后会把“模块怎么定义”这件事,打成一场牵动浏览器、Node、标准委员会、社区工具和整个包生态的长期内战。
如果把 JavaScript 江湖 写的是这门语言如何一路被推成平台语言,
那 模块化江湖 要写的,就是:
当它真的开始承受平台级复杂度之后,大家第一次为“怎么组织这门语言本身”狠狠干起来的那场仗。