CommonJS、AMD、CMD、UMD 与 ES Module 是怎样演进的?今天还要关心哪些差异?
先理解原理和示例,再练习完整口述与追问。
回答要点
CommonJS 服务端同步加载;AMD 依赖前置、CMD 就近依赖,解决浏览器异步加载;UMD 是兼容包装;ESM 是语言标准,静态结构、活绑定、可 Tree Shaking。今天主要关心 ESM 与 CommonJS 互操作。
原理拆解
CommonJS 用 require 同步读取并执行模块,module.exports 是导出对象,适合本地文件系统。浏览器下载脚本是异步的,于是出现 AMD(RequireJS):define(依赖数组, 工厂函数),依赖先加载再执行工厂;CMD(SeaJS)推崇在工厂内部用到时才 require,执行时机更靠后。UMD 不是新的模块系统,而是一段判断环境的包装代码,让同一个库同时支持 AMD、CommonJS 和全局变量。
ESM 由语言规范定义,import/export 必须写在顶层,依赖关系在执行前就能静态分析,所以构建工具能做 Tree Shaking;导入的是活绑定,导出方修改变量,导入方读到新值。浏览器通过 type="module" 原生加载 ESM,Node 通过 .mjs 或 package.json 的 "type": "module" 识别。AMD/CMD 已基本退出新项目,但旧库产物中仍会见到 UMD 包装。
示例与推演
(function (root, factory) {
if (typeof define === 'function' && define.amd) define([], factory)
else if (typeof module === 'object' && module.exports) module.exports = factory()
else root.MyLib = factory()
})(typeof self !== 'undefined' ? self : this, function () {
return { version: '1.0.0' }
})
这段包装依次检测 AMD 的 define、CommonJS 的 module,都不存在时挂到全局。现代库发布时更常见的是同时提供 ESM 与 CommonJS 产物,并用 exports 的 import/require 条件分别指向它们。
易错点
不要再把 CMD/AMD 当作当前项目选型;也不要说 ESM 只是“语法糖”。Node 中 require 加载 ESM 的能力与版本有关:较新的 Node(22.12+、20.19+)可直接 require 不含顶层 await 的 ESM 图,Node16 则会报错,需要动态 import()。
参考回答可直接练习口述
CommonJS 最早用于 Node,同步加载、运行时决定导出;浏览器为异步加载出现了 AMD 和 CMD,区别是依赖前置还是就近;UMD 是兼容多环境的包装。ESM 成为标准后,静态导入导出支持构建期分析和 Tree Shaking,导入是活绑定。现在我主要关注 ESM 与 CommonJS 的互操作:包的 exports 条件、默认导出差异,以及目标 Node 版本是否支持 require(esm)。