兰 亭 墨 苑
期货 · 量化 · AI · 终身学习
首页
归档
编辑文章
标题 *
URL 别名 *
内容 *
(支持 Markdown 格式)
这确实是前端历史上最漫长、最痛苦,但也最波澜壮阔的一场“统一战争”。 从 2009 年 Node.js 诞生,到 2025 年彻底完成转型,这场**“CommonJS (CJS) vs ES Modules (ESM)”** 的拉锯战持续了整整 15 年。 这不仅仅是技术之争,更是一场关于**标准话语权、向后兼容性与工程哲学**的政治博弈。我们可以把这个过程看作是“两个平行宇宙的艰难融合”。 --- ### 第一阶段:分裂的起源 (2009 - 2015) **“Node.js 被迫造轮子,浏览器在等待戈多”** * **Node.js 的困境:** 2009 年,Ryan Dahl 创造 Node.js 时,JavaScript 官方(ECMAScript)压根没有“模块”的概念。JS 只是给网页写写脚本的,谁能想到要用它写服务器? * 为了让开发者能把代码拆分成不同文件,Node.js 必须自己发明一套标准。 * 于是,**CommonJS (CJS)** 诞生了。它的标志是 `require()` 和 `module.exports`。 * **特点:** 它是**同步**的。因为服务器读本地硬盘的文件很快,同步加载没问题。 * **浏览器的困境:** 浏览器不能用 CJS。因为 `require('jquery')` 在浏览器里意味着要发起网络请求。如果同步等待网络请求,网页就会卡死(白屏)。 * 于是,社区发明了 AMD (RequireJS) 和 UMD。 * **局面:** 前端写 AMD,后端写 CJS。同一个 JS 语言,分裂成了两种完全不兼容的写法。 --- ### 第二阶段:一纸空文的“和平条约” (2015) **“ES6 发布,但没人能用”** 2015 年,ECMAScript 6 (ES2015) 正式发布。TC39 委员会宣布:**我们有了官方的模块标准了!也就是 `import` 和 `export`。** 大家都以为天亮了,结果却是长达数年的混乱: 1. **只是语法,不是实现:** ES6 规范只定义了怎么写(Syntax),没定义浏览器和 Node.js 怎么加载(Loader)。 2. **Node.js 的抗拒:** Node.js 团队看了 ES6 标准后非常头大。因为 **ESM 本质是静态且异步的**,而 Node.js 的 CJS 是动态且同步的。 * CJS: `if (true) { require('./a.js') }` —— 代码跑起来才知道引不引用。 * ESM: `import a from './a.js'` —— 代码运行前(编译时)就必须确定依赖关系。 * **冲突:** Node.js 无法直接支持 ESM,否则会破坏现有的数百万个 CJS 包。 --- ### 第三阶段:Node.js 的内战与“扩展名之乱” (2016 - 2019) **“要不要用 .mjs?要不要破坏兼容性?”** 这是最艰难的时期。Node.js 内部爆发了激烈的争论:如何让 ESM 和 CJS 在同一个运行时里共存? * **方案 A:** 智能识别。读取文件内容,如果有 `import` 就当 ESM,有 `require` 就当 CJS。 * **失败原因:** 性能太差,且有歧义。 * **方案 B(Michael Jackson Script):** 强行区分扩展名。CJS 用 `.js`,ESM 用 `.mjs`。 * **社区反弹:** 开发者极度反感 `.mjs` 这个丑陋的后缀。大家都在问:“为什么我写的是 JS,却不能用 .js 后缀?” * **妥协方案(`package.json`):** 最终,Node.js 12+ 引入了一个决定性的字段:`"type": "module"`。 * 如果你在 `package.json` 里写了这一行,整个项目的 `.js` 文件都被视为 ESM。 * 这也是今天所有现代项目的标配。 --- ### 第四阶段:浏览器的倒逼与 Vite 的助攻 (2019 - 2022) **“浏览器原生支持,Vite 降维打击”** 当 Node.js 还在纠结时,浏览器厂商(Chrome, Firefox, Safari)行动了。 * `<script type="module">` 被所有主流浏览器支持。 * **Vite 的出现是转折点:** Vite 证明了在开发环境直接使用浏览器原生 ESM 可以达到极致的快。这给了开发者巨大的动力去拥抱 ESM。 * **压力转移:** 以前是 Node.js 拖着不改,现在是前端开发者为了用 Vite,倒逼后端的工具库必须提供 ESM 版本。如果不提供,就被视为“过时”。 --- ### 第五阶段:填平最后一公里 (2023 - 2025) **“require(esm) 的达成与统一”** 虽然 ESM 成了主流,但还有一个巨大的痛点:**CJS 和 ESM 的互操作性(Interoperability)。** * **痛点:** 在 ESM 里可以 `import cjs`,但在 CJS 里**不能** `require(esm)`。因为 `require` 是同步的,而 ESM 加载被认为是异步的(Top-level await)。 * 这导致很多旧的 CJS 项目(比如旧的配置文件、CLI 工具)无法使用新的 Pure ESM 包。这被称为 **"Pure ESM 之痛"**(sindresorhus 等大佬带头只发 ESM 包,导致很多下游项目报错)。 * **2024-2025 的终局:** Node.js 团队终于攻克了最后的难关——**同步 `require(esm)`**。 * 通过对 V8 引擎和加载器的底层改造,Node.js 允许在一定条件下(没有顶层 await 时),让 CJS 代码同步加载 ESM 模块。 * **意义:** 这打通了任督二脉。开发者不再需要关心这个包是 CJS 还是 ESM,直接引用即可。生态割裂被物理弥合。 --- ### 总结:为什么 ESM 必须赢? 这场拉锯战之所以必须打,是因为 ESM 相比 CJS 有着**降维打击级的优势**,这些优势决定了它是未来的唯一真理: 1. **静态分析与 Tree Shaking:** 因为 `import` 是静态的(写在文件头部,不能放在 if 里),打包工具(Rollup/Vite)可以在代码运行前就画出精确的依赖图。**这让“摇树优化”(删掉没用的代码)成为可能。** CJS 做不到这一点,导致包体积无法缩减。 2. **异步加载与网络友好:** ESM 是为网络设计的。它支持异步加载,不会阻塞浏览器主线程。CJS 是为硬盘设计的,天生不适合浏览器。 3. **万能通用(Isomorphic):** ESM 是 JavaScript 语言标准(ECMA)。它是唯一能同时在**浏览器、Node.js、Deno、Bun、Cloudflare Workers** 上原生运行的格式。 ### 结语 从 2015 到 2025,JavaScript 社区用了 10 年时间,付出了巨大的迁移成本,终于把地基换了一遍。 现在的开发者是幸福的,因为你们不需要再写 `module.exports = ...`,也不用再纠结 AMD/UMD 的区别。当你敲下 `import` 的那一刻,你享受的是无数前人在这场“拉锯战”中争取来的**标准化红利**。
配图 (可多选)
选择新图片文件或拖拽到此处
标签
更新文章
删除文章