JavaScript 模块系统:CJS/AMD/UMD/ESM

article/2025/7/30 11:13:23

文章目录

  • 前言
  • 一、CommonJS (CJS) - Node.js 的同步模块系统
    • 1.1 设计背景
    • 1.2 浏览器兼容性问题
    • 1.3 Webpack 如何转换 CJS
    • 1.4 适用场景
  • 二、AMD (Asynchronous Module Definition) - 浏览器异步加载方案
    • 2.1 设计背景
    • 2.2 为什么现代浏览器不原生支持 AMD
    • 2.3 Webpack/Rollup 如何处理 AMD
    • 2.4 适用场景
  • 三、UMD (Universal Module Definition) - 兼容浏览器 + Node.js 的"缝合怪"
    • 3.1 设计背景
    • 3.2 为什么 UMD 代码看起来这么"丑"
    • 3.3 构建工具如何生成 UMD
    • 3.4 适用场景
  • 四、ES Modules (ESM) - 现代 JavaScript 标准
    • 4.1 设计背景
    • 4.2 为什么旧浏览器不支持 ESM
    • 4.3 构建工具如何处理 ESM
    • 4.4 ESM 模块生命周期的三个阶段
    • 4.5 适用场景
  • 五、模块系统对比总结
  • 总结


前言

模块系统是 JavaScript 生态演化的核心部分,不同的模块规范(CJS/AMD/UMD/ESM)针对不同的运行环境设计,它们的加载机制、语法规则和构建工具处理方式都有显著差异。本文将结合具体案例,详细解析它们的设计初衷、运行环境适配、构建工具转换规则,并解释为什么需要不同的打包策略。


一、CommonJS (CJS) - Node.js 的同步模块系统

1.1 设计背景

  1. 目标环境:Node.js(服务器端)
  2. 核心需求:同步加载模块,无需考虑网络延迟。(设计初衷是 Node.js 的本地文件系统)
  3. 关键特性:
    require() 同步阻塞:模块立即执行。
    module.exports 导出模块:用于模块化开发。
    模块缓存:相同路径的 require() 只执行一次。

1.2 浏览器兼容性问题

  • 为什么浏览器不能直接运行 CJS?
// 浏览器直接运行会报错!
const fs = require('fs'); // Uncaught ReferenceError: require is not defined

原因:

  1. API 不兼容:require 是 Node.js 的 API,浏览器没有实现。
  2. 加载方式差异:CJS 是同步加载,浏览器需要异步加载(否则阻塞渲染)。

1.3 Webpack 如何转换 CJS

// 原始代码 (CJS)
const lodash = require('lodash');
// Webpack 转换后(简化版)
const __webpack_modules__ = {'lodash': (module) => { module.exports = _; }
};
function __webpack_require__(moduleId) {// 1. 检查缓存// 2. 执行模块代码// 3. 返回 module.exports
}
const lodash = __webpack_require__('lodash');

关键转换策略

  • 包裹模块:每个模块被包裹成函数,避免全局污染。
  • 实现自己的 require 系统:webpack_require 模拟 Node.js 的模块加载。
  • 依赖分析:构建时静态分析 require() 调用。

1.4 适用场景

  1. Node.js 后端开发:适用于服务器端的模块化开发。
  2. 旧版工具链:如 Webpack 4 默认使用 CJS。

二、AMD (Asynchronous Module Definition) - 浏览器异步加载方案

2.1 设计背景

  1. 目标环境:浏览器(RequireJS)
  2. 核心需求:异步加载,避免阻塞渲染
  3. 关键特性:
    define() 定义模块
    require([], callback) 动态加载依赖
    依赖前置:所有依赖必须在回调函数之前声明

独立模块立即执行,依赖模块按序加载、回调执行

2.2 为什么现代浏览器不原生支持 AMD

<!-- 必须手动加载 RequireJS -->
<script src="require.js"></script>
<script>require(['jquery'], function($) {// 回调函数内才能使用 jQuery});
</script>

原因:

  1. AMD 是社区规范,不是 ECMAScript 标准
  2. 现代浏览器原生支持 ESM,不再需要 AMD

2.3 Webpack/Rollup 如何处理 AMD

// 原始 AMD 代码
define(['jquery'], function($) {return { init: () => $('body').css('color', 'red') };
});
// Webpack 转换后(Promise 化)
__webpack_require__.e("jquery").then(function() {const $ = __webpack_require__("jquery");return { init: () => $('body').css('color', 'red') };
});

关键转换策略:

  • 转为 Promise 链:适配现代异步编程
  • 代码拆分:动态加载的模块会被拆分为单独 chunk

2.4 适用场景

  1. 旧版浏览器项目(IE 8+)
  2. 按需加载的复杂 SPA(如 2015 年前的 AngularJS 项目)

三、UMD (Universal Module Definition) - 兼容浏览器 + Node.js 的"缝合怪"

3.1 设计背景

  1. 目标环境:同时支持浏览器、Node.js、AMD
  2. 核心需求:一份代码,多环境运行(适配所有规范)
  3. 关键特性:
    环境嗅探:判断当前是 CJS/AMD/全局变量
    手动适配:通过 if-else 实现多环境兼容

3.2 为什么 UMD 代码看起来这么"丑"

// UMD 模板代码(jQuery 风格)
(function (global, factory) {if (typeof define === 'function' && define.amd) {// AMD 环境define(['jquery'], factory);} else if (typeof exports === 'object') {// CJS 环境 (Node.js)module.exports = factory(require('jquery'));} else {// 浏览器全局变量global.myLib = factory(global.jQuery);}
}(typeof self !== 'undefined' ? self : this, function ($) {// 实际模块代码return { init: () => $('body').css('color', 'red') };
}));
</script>

原因:

  1. 需要手动判断运行环境
  2. 必须兼容多种模块加载方式

3.3 构建工具如何生成 UMD

# Rollup 生成 UMD
rollup -i src/index.js -o dist/bundle.umd.js -f umd -n myLib
// 输出结构
(function (global, factory) {// 环境检测逻辑...
})(this, function() {return /* 模块内容 */;
});

关键转换策略:

  • 包裹 IIFE(Immediately Invoked Function Expression,立即调用函数表达式):避免污染全局作用域
  • 注入环境判断:运行时动态选择模块系统

3.4 适用场景

  1. 开源库开发(如 Lodash、Moment.js)
  2. 需要同时支持<script>标签和 npm 安装的项目

传统浏览器用户:希望通过<script src="awesome-lib.js">直接使用
现代前端工程:希望通过 npm install awesome-lib 引入

用户环境不可控,而 UMD 就是最好的兼容方案
当使用 RollupWebpack 之类的打包器时,UMD 通常用作备用模块

四、ES Modules (ESM) - 现代 JavaScript 标准

4.1 设计背景

  1. 目标环境:现代浏览器 + Node.js(ES6+)
  2. 核心需求:官方标准、静态分析、Tree Shaking
  3. 关键特性:
    import / export 语法
    静态加载:依赖关系在编译时确定
    原生支持:浏览器和 Node.js 均可直接运行

静态分析(Static Analysis)是编程语言和构建工具在 不实际执行代码的情况下,通过分析代码的结构、语法、依赖关系来推导代码行为的技术。它在 Tree Shaking(摇树优化)中扮演核心角色,直接影响打包工具的无用代码消除能力。

静态分析是 现代前端工具链的基石

  • Tree Shaking 能安全删除未使用代码
  • 类型系统 能提前发现错误
  • 压缩工具 能极致优化体积

CJS 的动态特性破坏了静态分析的前提,而 ESM 的严格静态结构让工具能精确推导代码行为。这就是为什么现代前端生态(Vite/Rollup/Snowpack)都基于 ESM 设计。

模块系统静态分析可行性根本原因
ESM✅ 完美支持语言标准强制静态结构
CJS⚠️ 有限支持require() 动态性破坏分析前提
AMD✅ 基础支持依赖数组显式声明
UMD❌ 几乎不可用混合模式导致逻辑分裂
SystemJS❌ 不可用动态注册机制

4.2 为什么旧浏览器不支持 ESM

<!-- 现代浏览器 -->
<script type="module">import { add } from './math.js'; // 正常工作
</script>
<!-- 旧浏览器 -->
<script>import { add } from './math.js'; // SyntaxError
</script>

原因:

  1. import/export 是 ES6 语法,IE 11 及更早版本不支持
  2. 传统 <script> 默认是全局脚本,不解析模块语法

4.3 构建工具如何处理 ESM

webpack:

// 原始 ESM
import React from 'react';
// Webpack 转换后(CJS 风格)
const React = __webpack_require__('react');
  • 策略:默认转为 CJS,兼容旧环境

Vite:

// 开发模式:直接返回 ESM
import React from '/node_modules/react/index.js'; // 浏览器发起请求
// 生产模式:Rollup 打包
import { r as React } from './chunk-abc123.js';
  • 策略:
    开发模式:开发时不需要打包,利用浏览器原生 ESM
    生产模式:Rollup 打包优化

ESM 的模块处理机制与传统 AMD/CJS 有本质区别,主要体现在编译时静态分析运行时执行控制两个阶段。

4.4 ESM 模块生命周期的三个阶段

(1) 解析阶段(Parsing)
静态分析所有 import(无论是否会被执行)

// main.js
import { unused } from './unused.js'; // 即使从未使用也会被分析
import { core } from './core.js';
if (false) unused(); // 死代码

构建不可变的依赖图:
在这里插入图片描述
(2) 加载阶段(Loading)
浏览器/Node.js 的行为:

  • 立即并行请求所有依赖模块(包括unused.js)
  • 阻塞性:必须所有依赖加载完成才会进入执行阶段
示例网络请求:
GET /main.js
GET /unused.js (并行)
GET /core.js   (并行)

(3) 执行阶段(Evaluation)
严格按拓扑顺序初始化(从叶子节点开始):

执行顺序:unused.js → core.js → main.js
关键特性:
即使模块导出未被使用,该模块仍会被执行(包括其顶层代码)
但不会执行未被调用的函数

模块初始化 ≠ 导出被调用
即使导出未被使用,模块的顶层代码仍会执行(打包工具可能通过Tree Shaking移除)

4.5 适用场景

  1. 现代前端项目(React/Vue 3+)
  2. Node.js 14+ 后端项目

五、模块系统对比总结

模块系统加载方式环境支持构建工具转换策略典型应用场景
CJS同步Node.js包裹为函数,模拟 requireNode.js 后端
AMD异步浏览器 (RequireJS)转为 Promise + 代码拆分旧版浏览器 SPA
UMD兼容多种浏览器 + Node.jsIIFE + 环境嗅探开源库开发
ESM静态现代浏览器 + Node原生支持或转为 CJS现代前端/Node 项目
特性ESMAMD/RequireJSCommonJS
依赖获取时机并行请求所有发现的依赖按需并行请求同步阻塞加载
执行触发条件整个依赖图就绪后拓扑序执行串行回调(按照申明顺序)遇到 require 时立即执行
循环依赖处理语言标准明确定义(引用未初始化值)依赖加载器实现(可能不一致)部分导出可能为 undefined
典型场景import './module.js'require(['module'], callback)const m = require('./module')

总结

  • CJS:Node.js 专用,同步加载,需构建工具转换才能在浏览器运行
  • AMD:旧浏览器异步加载方案,已被 ESM 取代
  • UMD:兼容浏览器 + Node.js 的过渡方案,适合库开发
  • ESM:现代标准,支持 Tree Shaking,未来唯一选择

构建工具的作用就是抹平环境差异,让开发者可以用任意模块规范编写代码,最终输出目标环境可运行的版本。


http://www.hkcw.cn/article/nDieJQuabh.shtml

相关文章

乌称摧毁34%俄远程机队 俄媒否认 谎言蛛网行动

俄罗斯“与假新闻作战”网站发布文章称,通过分析乌克兰方面发布的视频可以确认,乌总统泽连斯基关于“已摧毁34%俄罗斯远程机队”的说法并不属实。俄方认为,乌克兰实际上可能仅摧毁了两架图-95战略轰炸机及一架安-12运输机,其余受损飞机在维修后均可恢复作战能力。乌克兰国家…

加沙停火协议为何一波三折 美斡旋遇阻

本周,美国就巴勒斯坦伊斯兰抵抗运动(哈马斯)和以色列的停火展开斡旋,提出一项为期60天的加沙地带停火方案。然而,围绕是否接受这份方案,哈马斯和以色列的态度不一,谈判频频出现变数。美国白宫5月29日表示,以色列已接受并签署美国提出的加沙地带临时停火方案。但该方案在…

基于springboot的宠物领养系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

中国王朝简史

文章目录 一、先秦时期&#xff1a;文明起点与制度雏形夏&#xff08;约前2070年–前1600年&#xff09;商&#xff08;约前1600年–前1046年&#xff09;周&#xff08;前1046年–前256年&#xff09; 二、大一统帝国的试验与成熟秦&#xff08;前221年–前207年&#xff09;汉…

Freefilesync配置windows与windows,windows与linux之间同步

说明 Freefilesync&#xff1a;用于windows与windows&#xff0c;windows与linux之间同步 linux 之间同步&#xff0c;使用系统的自带的 corn 软件&#xff0c;执行 sync 命名的脚本即可 一 、下载Freefilesync windows服务器上打开官网 https://freefilesync.org/&#xff0…

数字创新智慧园区建设及运维方案

该文档是 “数字创新智慧园区” 建设及运维方案,指出传统产业园区存在管理粗放等问题,“数字创新园区” 通过大数据、AI、物联网、云计算等数字化技术,旨在提升园区产业服务、运营管理水平,增强竞争力,实现绿色节能、高效管理等目标。建设内容包括智能设施、核心支撑平台、…

P1541 [NOIP 2010 提高组] 乌龟棋

P1541 [NOIP 2010 提高组] 乌龟棋 - 洛谷 题目背景 NOIP2010 提高组 T2 题目描述 小明过生日的时候&#xff0c;爸爸送给他一副乌龟棋当作礼物。 乌龟棋的棋盘是一行 N 个格子&#xff0c;每个格子上一个分数&#xff08;非负整数&#xff09;。棋盘第 1 格是唯一的起点&a…

设计模式——享元设计模式(结构型)

摘要 享元设计模式是一种结构型设计模式&#xff0c;旨在通过共享对象减少内存占用和提升性能。其核心思想是将对象状态分为内部状态&#xff08;可共享&#xff09;和外部状态&#xff08;不可共享&#xff09;&#xff0c;并通过享元工厂管理共享对象池。享元模式包含抽象享…

Qt OpenGL编程常用类

Qt提供了丰富的类来支持OpenGL编程&#xff0c;以下是常用的Qt OpenGL相关类&#xff1a; 一、QOpenGLWidget 功能&#xff1a;用于在 Qt 应用程序中嵌入 OpenGL 渲染的窗口部件。替代了旧版的QGLWidget。提供了OpenGL上下文和渲染表面。 继承关系&#xff1a;QWidget → QOp…

【JMeter】性能测试知识和工具

目录 何为系统性能 何为性能测试 性能测试分类 性能测试指标 性能测试流程 性能测试工具&#xff1a;JMeter&#xff08;主测web应用&#xff09; jmeter文件目录 启动方式 基本元件&#xff1a;元件内有很多组件 jmeter参数化 jmeter关联 自动录制脚本 直连数据库…

[Linux] nginx源码编译安装

初次学习&#xff0c;如有错误欢迎指正 目录 环境包部署 创建程序用户 软件包压缩 配置 编译 安装 建立快捷启动 启动nginx&#xff1f; 防火墙管理 查看规则 清空规则 关闭服务 开启服务 查看状态 开机自启 开机禁用 查看开机启动状态 nginx&#xff0c;启…

Spring AI Image Model、TTS,RAG

文章目录 Spring AI Alibaba聊天模型图像模型Image Model API接口及相关类实现生成图像 语音模型Text-to-Speech API概述实现文本转语音 实现RAG向量化RAGRAG工作流程概述实现基本 RAG 流程 Spring AI Alibaba Spring AI Alibaba实现了与阿里云通义模型的完整适配&#xff0c;…

多地机关食堂端午假期向社会开放 特色套餐迎客来

端午假期期间,全国多地政府机关食堂面向社会公众开放。5月31日中午,荣昌区政府机关食堂如约向游客开放,首日第一餐吸引了超过3000名游客前来体验。荣昌区特别推出了61元的“六一”家庭套餐,包含荣昌卤鹅、猪油泡粑、黄凉粉等特色菜品,还新增了粽子和儿童喜欢的薯条、鸡腿、…

韩国大选“5选1”投票将启 三强格局形成

6月3日,韩国将迎来新一届总统选举。最初有7名候选人登记参选,但截至6月2日,已有两名候选人宣布退出,形成了“5选1”的局面。目前李在明、金文洙和李俊锡基本形成三强格局。4名韩国前总统也各自进行着“路演”,通过各种方式表达对各自阵营候选人的支持。尹锡悦5月31日表态支…

美联邦调查局称科罗拉多州发生恐袭 燃烧瓶袭击游行人群

美国联邦调查局(FBI)局长卡什帕特尔在社交媒体上表示,6月1日科罗拉多州博尔德市发生了一起有针对性的恐怖袭击事件。FBI正在对此进行全面调查。FBI特工和当地执法人员已到达案发现场,并将在获得更多信息后分享最新情况。同日下午,科罗拉多州博尔德市的一个购物中心发生了袭…

第二轮谈判 乌公布代表团14人名单 防长继续带队

俄罗斯代表团已抵达土耳其伊斯坦布尔,准备参加即将举行的俄乌谈判。俄谈判代表团团长梅金斯基在抵达后表示,关于乌克兰谈判的所有评论将在6月2日公布,并会在当天详细说明俄罗斯在乌克兰问题上的立场。对于乌克兰对俄罗斯境内目标可能发起的攻击及其影响,俄方代表团成员、俄…

MQTT入门实战宝典:从零起步掌握物联网核心通信协议

MQTT入门实战宝典&#xff1a;从零起步掌握物联网核心通信协议 前言 物联网时代&#xff0c;万物互联已成为现实&#xff0c;而MQTT协议作为这个时代的"数据总线"&#xff0c;正默默支撑着从智能家居到工业物联的各类应用场景。本文将带你揭开MQTT的神秘面纱&#…

腾讯位置商业授权行政区划开发指南

概述 本服务提供中国标准行政区划数据查询功能&#xff0c;支持&#xff1a; 1 . 全国省、市、区/县、乡镇/街道 四级行政区划数据&#xff1b; 2 . 支持三级区划&#xff08;省/市 - 区/县&#xff09;轮廓数据&#xff1b; 3 . 支持区划查询、省市区列表、查询子级区划等功能…

GIS数据类型综合解析

GIS数据类型综合解析 目录 GIS数据类型综合解析1. 总体介绍2. GIS数据类型分类与对比2.1 主要数据类型对比表 3. 详细解析与扩展内容3.1 矢量数据&#xff08;Vector Data&#xff09;3.2 栅格数据&#xff08;Raster Data&#xff09;3.3 属性数据&#xff08;Attribute Data&…

Spring框架学习day5--AOP概念以及示例实现

AOP(面向切面编程) 1.概述 AOP为AspectOrientedProgramming 的缩写&#xff0c;意为&#xff1a;面向切面编程&#xff0c;通过 预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。 AOP是OOP的延续&#xff0c;是软件开发中的一个热点&#xff0c;是java开发中的…