梳理一下前端模块化规范:CommonJS ESM AMD CMD UMD
前端模块化规范在发展过程中出现过多种规范大多开发者都对这些名词有个印象但问起来又有些模糊。本文的目的是做一个梳理帮助记忆。先上一张对比表类型核心定位语法关键词适用环境特点CommonJSCJSNode.js 默认模块规范require、module.exports、exportsNode.js 服务端、Webpack 打包同步加载、运行时加载、浅拷贝ESM官方标准模块规范import、export、export default现代浏览器、Node.js v14异步 / 静态加载、编译时确定依赖、绑定引用AMD异步模块规范define、require旧版前端项目RequireJS依赖前置、异步加载、不阻塞页面CMD异步模块规范define、require旧版前端项目SeaJS依赖就近、按需加载、风格接近 NodeUMD兼容模块格式自执行函数包裹浏览器全局、AMD、CommonJS自动判断环境、一套代码多环境可用Dual Package双产物发包策略打出 2 个包npm 库、多环境兼容项目同时输出 CJS 和 ESM 产物工具自动识别一、CommonJSCJSCommonJS 是 Node.js 的默认模块化规范也是早期前端打包支持的规范很多老 Node 项目至今还在使用。// 导出模块a.js // 方式1整体导出 module.exports { add: (a, b) a b, name: CommonJS模块 }; // 方式2单个导出 exports.sub (a, b) a - b; // 导入模块b.js const mod require(./a.js); console.log(mod.add(1, 2)); // 3 console.log(mod.sub(3, 1)); // 2特点同步加载在 Node.js 服务端文件都在本地同步加载不会有问题。浅拷贝导出的是对象浅拷贝后续原模块修改该值导入方拿到的副本不会变。补充CommonJS 没有官方缩写但行业内普遍简称 CJS。二、ESM —— 官方标准ESMES Modules是 ECMAScript 官方推出的模块化规范也是目前前端开发的主流 —— 现代浏览器、Node.jsv14、Vue/React 等框架全部默认支持 ESM。它解决了 CommonJS 的诸多痛点比如同步加载、不支持 Tree-Shaking 等语法也更简洁。// 导出模块a.js // 方式1命名导出可多个 export const add (a, b) a b; export const name ESM模块; // 方式2默认导出仅1个 export default function sub(a, b) { return a - b; } // 导入模块b.js import { add, name } from ./a.js; import sub from ./a.js; console.log(add(1, 2)); // 3 console.log(sub(3, 1)); // 2特点静态编译编译时就确定依赖关系支持 Tree-Shaking删除无用代码打包体积更小浏览器中是异步加载不阻塞页面。绑定引用导出的是值的引用原模块修改该值导入方也会同步变化和 CommonJS 的 “浅拷贝” 区分。三、AMD CMDAMDAsynchronous Module Definition代表工具是 RequireJS核心是依赖前置—— 一开始就声明所有依赖异步加载完成后执行回调。CMDCommon Module Definition代表工具是 SeaJS核心是依赖就近—— 用到某个模块时再去 require写法风格接近 Node 的 CommonJS。AMD 是依赖前置、提前加载CMD 是依赖就近、懒执行两者现在都已淘汰不必深入学习。四、UMDUMDUniversal Module Definition直译是 “通用模块定义”它不是一种新的规范而是一种兼容方案—— 一套代码能同时适配 CommonJS、AMD、浏览器全局变量。自动判断当前运行环境选择对应的模块化方式。标准无依赖 UMD 写法(function (root, factory) { // AMD if (typeof define function define.amd) { define(factory); } // CommonJS else if (typeof module object module.exports) { module.exports factory(); } // 浏览器全局 else { root.MyModule factory(); } })(this, function () { // 模块逻辑 return { add: (a, b) a b }; });特点万能兼容一套打包产物能在任何环境运行但缺点是代码冗余现在的开源库已经很少用 UMD转而用更简洁的 Dual Package。五、Dual Package —— 双产物包它是一种发包策略同时打包输出 CJS 和 ESM 两种产物让项目既能支持 CommonJS也能支持 ESM。现代 npm 库的标准配置{ name: my-utils, main: dist/index.cjs, module: dist/index.mjs, exports: { .: { import: ./dist/index.mjs, require: ./dist/index.cjs } } }优势不用写环境判断代码比 UMD 更简洁适配所有现代项目和老 Node 项目是目前开源库的主流发包方式。六、package.json 的 type 字段type是 Node.js 官方字段用于声明模块化格式影响.js文件的解析方式。type: module按 ESM 解析不配置默认按 CommonJS 解析七、Webpack 打包指定输出规范Webpack 5 推荐使用output.library.type输出 CommonJSmodule.exports { output: { filename: bundle.js, library: { type: commonjs2 } } };输出 ESMmodule.exports { output: { filename: bundle.mjs, library: { type: module } }, experiments: { outputModule: true } };输出 UMDmodule.exports { output: { library: { type: umd } } };输出双产物分别用两份配置打包两次即可。总结前端项目Vue/React/Vite/Webpack一律用 ESM。Node.js 项目老项目用 CommonJS新项目推荐 ESM。开发 npm 库使用 Dual Package 双产物极老环境可额外打包 UMD。