【导入模式】AMD & ESM & CJS & UMD区别

118 阅读3分钟

突然有点模糊了,故浅总结下这几个导入模式的区别😂

一、核心特性对比表

特性CommonJS (CJS)AMDESM (ES Modules)UMD
加载方式同步 (Node.js)异步 (浏览器优先)静态/动态 (原生支持)环境自适应
语法require/module.exportsdefine/requireimport/exportIIFE 包裹多种环境判断
作用域模块作用域函数作用域模块作用域全局变量或模块作用域
适用环境Node.js 服务端浏览器 (RequireJS)现代浏览器/Node.js 14+跨环境兼容
Tree-Shaking困难困难原生支持困难
典型应用npm 包开发旧浏览器项目现代前端框架通用库开发

二、CommonJS (CJS)

javascript
// 导出
module.exports = { key: 'value' };
exports.fn = () => {};

// 导入
const lib = require('./lib');
const { fn } = require('./utils');

技术原理

  • 设计于 2009 年,Node.js 默认模块系统
  • 同步加载导致浏览器端性能问题
  • 模块缓存机制 (require.cache)

现代应用

  • 仍主导 Node.js 生态 (85%+ npm 包使用 CJS)
  • 需配合 @babel/plugin-transform-modules-commonjs 转换 ESM

三、AMD (Asynchronous Module Definition)

javascript
// 定义模块
define('moduleId', ['dep1', 'dep2'], (d1, d2) => {
  return { method: () => d1.action() };
});

// 加载模块
require(['module'], (mod) => mod.method());

技术原理

  • RequireJS 实现的浏览器优先方案
  • 依赖前置声明 + 异步加载
  • 适合网络环境差的旧浏览器场景

现代替代

  • 逐步被 <script type="module"> 原生 ESM 替代
  • Webpack 等工具可输出 AMD 格式供旧系统使用

四、ESM (ECMAScript Modules)

javascript
// 导出
export const name = 'ESM';
export default function() {};

// 导入
import { name } from './module.js';
import lib from './lib.js';

核心优势

  1. 静态结构:编译时确定依赖关系,支持 Tree-Shaking
  2. 浏览器原生支持:无需打包工具直接运行 (Chrome 61+ / Firefox 60+)
  3. 循环引用处理:通过实时绑定 (Live Binding) 安全处理
  4. 动态导入import() 实现代码分割

Node.js 集成

json
// package.json
{
  "type": "module",  // 启用 ESM
  "exports": {
    ".": {
      "import": "./esm/index.js",  // ESM 入口
      "require": "./cjs/index.cjs" // CJS 回退
    }
  }
}

五、UMD (Universal Module Definition)

javascript
(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD
    define(['dep'], factory);
  } else if (typeof exports === 'object') {
    // CJS
    module.exports = factory(require('dep'));
  } else {
    // 全局
    root.MyLib = factory(root.Dep);
  }
})(this, (dep) => {
  // 模块逻辑
  return { /* ... */ };
});

设计哲学

  • 兼容层模式:一套代码适配 AMD/CJS/全局变量
  • 文件体积通常比单环境格式大 30%+ (含环境检测代码)

现代应用场景

  • 需通过 <script> 标签直接引入的公共库 (如图表库)
  • 微前端架构中的跨技术栈模块共享

六、模块转换关系图

graph LR
  A(CJS) -->|Babel/ESBuild| D(ESM)
  B(AMD) -->|Webpack| D
  D -->|Rollup| C(UMD)
  C -->|Tree-Shaking| D

七、工具链支持对比

工具CJS 支持AMD 支持ESM 支持UMD 支持
Webpack✅ 原生✅ 插件✅ 5.0+ 原生✅ 配置输出格式
Rollup✅ 插件✅ 插件✅ 原生✅ 配置输出格式
Vite✅ 预构建转换❌ 需插件✅ 原生✅ 构建输出选项
ESBuild✅ 自动转换✅ 原生✅ 格式选项

八、选型建议指南

  1. 新项目开发

    • 前端项目 → 原生 ESM + Vite
    • Node.js 服务 → 混合 ESM/CJS (逐步迁移)
  2. 库开发

    • 公共库 → UMD + ESM 双格式发布
    • 私有库 → ESM 单格式
  3. 遗留系统维护

    • 浏览器端 → AMD → 用 Webpack 转换为 ESM
    • Node.js → CJS → 增量迁移为 ESM
  4. 性能关键型

    • 浏览器端 → ESM 原生加载 + HTTP/2
    • 服务端 → CJS (同步加载无性能瓶颈)

九、代码示例:现代库的多格式发布

项目结构

dist/
  ├── my-lib.umd.js     # UMD 格式
  ├── my-lib.esm.js     # ESM 格式
  └── my-lib.cjs.js     # CJS 格式

package.json 配置

json
{
  "name": "my-lib",
  "main": "dist/my-lib.cjs.js",
  "module": "dist/my-lib.esm.js",
  "browser": "dist/my-lib.umd.js",
  "exports": {
    ".": {
      "import": "./dist/my-lib.esm.js",
      "require": "./dist/my-lib.cjs.js"
    }
  }
}