JavaScript 模块系统:CJS/AMD/UMD/ESM
文章目录
- 前言
- 一、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 设计背景
- 目标环境:Node.js(服务器端)
- 核心需求:同步加载模块,无需考虑网络延迟。(设计初衷是 Node.js 的本地文件系统)
- 关键特性:
require() 同步阻塞:模块立即执行。
module.exports 导出模块:用于模块化开发。
模块缓存:相同路径的 require() 只执行一次。
1.2 浏览器兼容性问题
- 为什么浏览器不能直接运行 CJS?
// 浏览器直接运行会报错!
const fs = require('fs'); // Uncaught ReferenceError: require is not defined
原因:
- API 不兼容:require 是 Node.js 的 API,浏览器没有实现。
- 加载方式差异: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 适用场景
- Node.js 后端开发:适用于服务器端的模块化开发。
- 旧版工具链:如 Webpack 4 默认使用 CJS。
二、AMD (Asynchronous Module Definition) - 浏览器异步加载方案
2.1 设计背景
- 目标环境:浏览器(RequireJS)
- 核心需求:异步加载,避免阻塞渲染
- 关键特性:
define() 定义模块
require([], callback) 动态加载依赖
依赖前置:所有依赖必须在回调函数之前声明
独立模块
立即执行,依赖模块
按序加载、回调执行
2.2 为什么现代浏览器不原生支持 AMD
<!-- 必须手动加载 RequireJS -->
<script src="require.js"></script>
<script>require(['jquery'], function($) {// 回调函数内才能使用 jQuery});
</script>
原因:
- AMD 是社区规范,不是 ECMAScript 标准
- 现代浏览器原生支持 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 适用场景
- 旧版浏览器项目(IE 8+)
- 按需加载的复杂 SPA(如 2015 年前的 AngularJS 项目)
三、UMD (Universal Module Definition) - 兼容浏览器 + Node.js 的"缝合怪"
3.1 设计背景
- 目标环境:同时支持浏览器、Node.js、AMD
- 核心需求:一份代码,多环境运行(适配所有规范)
- 关键特性:
环境嗅探:判断当前是 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>
原因:
- 需要手动判断运行环境
- 必须兼容多种模块加载方式
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 适用场景
- 开源库开发(如 Lodash、Moment.js)
- 需要同时支持
<script>
标签和npm 安装
的项目
传统浏览器用户:希望通过
<script src="awesome-lib.js">
直接使用
现代前端工程:希望通过npm install awesome-lib
引入
用户环境不可控,而 UMD 就是最好的兼容方案
当使用 RollupWebpack 之类的打包器时,UMD 通常用作备用模块
四、ES Modules (ESM) - 现代 JavaScript 标准
4.1 设计背景
- 目标环境:现代浏览器 + Node.js(ES6+)
- 核心需求:官方标准、静态分析、Tree Shaking
- 关键特性:
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>
原因:
- import/export 是 ES6 语法,IE 11 及更早版本不支持
- 传统
<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 适用场景
- 现代前端项目(React/Vue 3+)
- Node.js 14+ 后端项目
五、模块系统对比总结
模块系统 | 加载方式 | 环境支持 | 构建工具转换策略 | 典型应用场景 |
---|---|---|---|---|
CJS | 同步 | Node.js | 包裹为函数,模拟 require | Node.js 后端 |
AMD | 异步 | 浏览器 (RequireJS) | 转为 Promise + 代码拆分 | 旧版浏览器 SPA |
UMD | 兼容多种 | 浏览器 + Node.js | IIFE + 环境嗅探 | 开源库开发 |
ESM | 静态 | 现代浏览器 + Node | 原生支持或转为 CJS | 现代前端/Node 项目 |
特性 | ESM | AMD/RequireJS | CommonJS |
---|---|---|---|
依赖获取时机 | 并行请求所有发现的依赖 | 按需并行请求 | 同步阻塞加载 |
执行触发条件 | 整个依赖图就绪后拓扑序执行 | 串行回调(按照申明顺序) | 遇到 require 时立即执行 |
循环依赖处理 | 语言标准明确定义(引用未初始化值) | 依赖加载器实现(可能不一致) | 部分导出可能为 undefined |
典型场景 | import './module.js' | require(['module'], callback) | const m = require('./module') |
总结
- CJS:Node.js 专用,同步加载,需构建工具转换才能在浏览器运行
- AMD:旧浏览器异步加载方案,已被 ESM 取代
- UMD:兼容浏览器 + Node.js 的过渡方案,适合库开发
- ESM:现代标准,支持 Tree Shaking,未来唯一选择
构建工具的作用就是
抹平环境差异
,让开发者可以用任意模块规范编写代码,最终输出目标环境可运行的版本。
相关文章:

JavaScript 模块系统:CJS/AMD/UMD/ESM
文章目录 前言一、CommonJS (CJS) - Node.js 的同步模块系统1.1 设计背景1.2 浏览器兼容性问题1.3 Webpack 如何转换 CJS1.4 适用场景 二、AMD (Asynchronous Module Definition) - 浏览器异步加载方案2.1 设计背景2.2 为什么现代浏览器不原生支持 AMD2.3 Webpack/Rollup 如何处…...

STM32F407寄存器操作(ADC非连续扫描模式)
1.前言 书接上回,在看手册的时候我突然发现手册上还描述了另一种ADC扫描模式,即非连续扫描模式,想着连续扫描模式都已经探索过了,那就顺手把非非连续模式研究一下吧。 2.理论 我们先看看手册,这里我就以规则通道举例…...
生产系统中TongWeb故障应急处理办法
本文档主要说明在上线正式运行的系统中,若TongWeb或部署在TongWeb上的应用出现问题时,现场维护人员或在现场的TongWeb支持人员应当采取的处理步骤。 工作基本原则: 任何操作必须经过项目相关负责人同意后进行,禁止在未允许的情况…...

PHP学习笔记(十一)
类常量 可以把在类中始终保持不变的值定义为常量,类常量的默认可见性是public。 接口中也可以定义常量。 可以用一个变量来动态调用类,但该变量的值不能为关键字 需要注意的是类常量只为每个类分配一次,而不是为每个类的实例分配。 特殊的…...
PyTorch中 torch.utils.data.DataLoader 的详细解析和读取点云数据示例
一、DataLoader 是什么? torch.utils.data.DataLoader 是 PyTorch 中用于加载数据的核心接口,它支持: 批量读取(batch)数据打乱(shuffle)多线程并行加载(num_workers)自…...

直线模组在手术机器人中有哪些技术挑战?
手术机器人在现代医疗领域发挥着越来越重要的作用,直线模组作为其关键部件,对手术机器人的性能有着至关重要的影响。然而,在手术机器人中使用直线模组面临着诸多技术挑战,具体如下: 1、高精度要求:手术…...

RK3568DAYU开发板-平台驱动开发--UART
1、程序介绍 本程序是基于OpenHarmony标准系统编写的平台驱动案例:UART 系统版本:openharmony5.0.0 开发板:dayu200 编译环境:ubuntu22 部署路径: //sample/06_platform_uart 2、基础知识 2.1、UART简介 UART指异步收发传输器(Univer…...
ubuntu 安装 Redis 5.0.8 的完整步骤
以下是根据前面的沟通记录整理的完整安装过程和依赖项,确保在 Ubuntu 22 上成功安装 Redis 5.0.8。 安装 Redis 5.0.8 的完整步骤 1. 安装依赖 在编译和运行 Redis 之前,需要安装一些必要的工具和库: sudo apt update sudo apt install bu…...

制造企业搭建AI智能生产线怎么部署?
制造商需要精准协调生产和发货,确保订单及时交付。MES、ERP、CRM 系统与生产线集成,对生产管理流程、物料跟踪、品控、确定货期至关重要。如果某个系统发生延迟或者效率低下,会在造成整个生产环节停滞,影响最终交付,导…...

深度学习驱动的超高清图修复技术——综述
Deep Learning-Driven Ultra-High-Definition Image Restoration: A Survey Liyan Wang, Weixiang Zhou, Cong Wang, Kin-Man Lam, Zhixun Su, Jinshan Pan Abstract Ultra-high-definition (UHD) image restoration aims to specifically solve the problem of quali…...
unix/linux source 命令,其内部结构机制
要理解 source (或 .) 命令的内部结构机制,我们需要戴上“操作系统”和“解释器设计”的眼镜,深入到 Shell 如何管理其状态以及如何执行命令的层面。 虽然我们无法直接看到 Shell 内部的 C 代码(除非我们去阅读 Bash 或 Zsh 的源码),但我们可以基于其行为和操作系统的原理…...
【LLM】FastAPI入门教程
note FastAPI 是一个现代的、快速(高性能)的 Web 框架,用于构建 API(应用程序编程接口)。它基于 Python 3.7,使用了 Python 类型提示(type hints),并且具有自动化的文档…...
进程同步机制-信号量机制-记录型信号量机制中的的wait和signal操作
wait和signal是记录型信号量机制中用于实现进程同步与互斥的两个重要操作, wait 操作 wait(semaphores *S) {S->value --;if (S->value<0) block(S->list) }请求资源:S->value --; 这一步表示进程请求一个单位的资源,将信号…...
gitlib 常见命令
git clone <项目URL> # 从 GitLab 拉取代码到本地 git status 查看状态 git diff 文件路径 查看修改位置 git diff 文件路径 查看修改位置 black -l 180 路径 格式化文件 git add 路径 (可以多个) 添加修改到暂存区 git commit -m “提交说明…...

Azure DevOps 管道部署系列之二IIS
本博客旨在提供如何使用 Azure DevOps YAML 管道部署到虚拟机上的 IIS 的实用指南。 开始之前,您需要做好以下准备: 您拥有要部署的服务器的访问权限以及 PowerShell 的管理员访问权限。您拥有要部署的远程服务器的互联网访问权限。您拥有在服务器上安装 .NET Core 托管包的…...

Vue.js教学第十七章:Vue 与后端交互(一),Axios 基础
Vue 与后端交互(一):Axios 基础 在现代前端开发中,Vue 应用通常需要与后端 API 进行数据交互,以实现动态数据的获取和提交。Axios 是一个基于 Promise 的 HTTP 客户端,广泛用于 Vue 项目中与后端进行通信。本文将深入讲解 Axios 的基本用法,包括如何通过 Axios 发送 GE…...

人工智能浪潮下,制造企业如何借力DeepSeek实现数字化转型?
一、DeepSeek技术概述 DeepSeek,凭借其强大的深度学习和自然语言处理能力,能够理解复杂问题并提供精准解决方案。它不仅能够作为学习、工作、生活的助手,满足用户在不同场景下的需求,更能在制造业中发挥重要作用。通过自然语言交…...
NodeJS全栈开发面试题讲解——P2Express / Nest 后端开发
✅ 2.1 Express 的中间件机制?如何组织一个 RESTful API 项目? 面试官好,我来讲讲 Express 的中间件机制,它是 Express 架构的核心,也是组织 RESTful 项目的基础。 🧩 什么是中间件? 中间件&am…...

从线性代数到线性回归——机器学习视角
真正不懂数学就能理解机器学习其实是个神话。我认为,AI 在商业世界可以不懂数学甚至不懂编程也能应用,但对于技术人员来说,一些基础数学是必须的。本文收集了我认为理解学习本质所必需的数学基础,至少在概念层面要掌握。毕竟&…...

计算机网络相关发展以及常见性能指标
目录 一、因特网概述 1.1 基本概念 1.2 因特网发展的三个阶段 1.3 英特网服务提供者ISP 1.4 英特网的标准化工作 1.5 因特网的组成 1.6 简单总结 二、3种交换方式 2.1 电路交换(Circuit Switching) 2.2 分组交换(Packet Switching&…...

通义灵码:基于MCP的火车票小助手系统全流程设计与技术总结
具体操作步骤请访问:https://blog.csdn.net/ailuloo/article/details/148319336?spm1001.2014.3001.5502 前沿技术应用全景图 一、项目背景与需求分析 目标:基于12306 MCP接口,开发一款解决高峰出行(春运/节假日)痛…...

为什么建立 TCP 连接时,初始序列号不固定?
主要原因有两个方面: 很大程度上避免历史报文被下一个相同四元组的 TCP 连接接收问题(主要方面)防止黑客伪造相同序列号的 TCP 报文被接收 接下来,详细说说第一点 假设每次建立 TCP 连接时,客户端和服务端的初始序列…...

VBA数据库解决方案二十:Select表达式From区域Where条件Order by
《VBA数据库解决方案》教程(版权10090845)是我推出的第二套教程,目前已经是第二版修订了。这套教程定位于中级,是学完字典后的另一个专题讲解。数据库是数据处理的利器,教程中详细介绍了利用ADO连接ACCDB和EXCEL的方法…...

NX753NX756美光科技闪存NX784NX785
技术解读与产品特性 美光科技的NX系列闪存,包括NX753、NX756、NX784、NX785等型号,代表了当前存储技术的前沿水平。这些产品基于先进的NAND闪存技术,采用业界领先的3D TLC NAND技术,实现了高速的数据读写能力。3D TLC NAND技术通…...

使用 pytesseract 构建一个简单 OCR demo
简介 pytesseract 库是 Google Tesseract OCR (光学字符识别)引擎的一个 Python 封装库,使用广泛且功能强大。 构建 使用 pytesseract 构建一个简单 OCR demo。 步骤一:安装必要的库 您需要在您的 Python 环境中安装 pytessera…...
Cesium快速入门到精通系列教程三:添加物体与3D建筑物
Cesium中添加物体与3D建筑物,对于大规模城市模型,推荐使用 3D Tileset;对于简单几何图形,可以使用 Entity API;对于复杂模型,可以使用 GLTF 格式: 一、添加一个点: 在 Cesium 1.93…...

git 如何解决分支合并冲突(VS code可视化解决+gitLab网页解决)
1、定义:两个分支修改了同一文件的同一行代码,无法自动决定如何合并代码,需要人工干预的情况。(假设A提交了文件a,此时B在未拉取代码的情况下,直接提交是会报错的,此时需要拉取之后再提交才会成功ÿ…...

【CF】Day72——Codeforces Round 890 (Div. 2) CDE1 (二分答案 | 交互 + 分治 | ⭐树上背包)
C. To Become Max 题目: 思路: 二分挺好想的,但是check有点不好写 看到最大值,试试二分,如果 x 可以,那么 x - 1 肯定也可以,所以具有单调性,考虑二分 如何check呢?由于…...

单片机寄存器的四种主要类型!
1. 控制寄存器(Control Registers) 专业定义:用于配置硬件行为或触发操作的寄存器。 大白话: 相当于设备的“控制面板”,通过写入特定值来开关功能或调整参数。例如&am…...

智能嗅探AJAX触发:机器学习在动态渲染中的创新应用
一、问题描述:数据加载变“隐形”,采集举步维艰 随着Web技术不断发展,越来越多网站采用了AJAX、动态渲染等技术来加载数据。以今日头条(https://www.toutiao.com)为例,用户打开网页时并不会一次性加载所有…...