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 设计背景

  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 安装的项目
export function kInstallScript(src: string): Promise<void> {return new Promise((resolve, reject) => {const script = document.createElement('script');script.src = src;script.onload = resolve as () => void;script.onerror = reject;document.head.append(script);});
}
  await kInstallScript(kIsMobile? '/lib/mobileFilePreview/file-preview.umd.min.js': '/file-preview/filePreview.umd.min.js');kFilePreviewSDK = kIsMobile? (globalThis as any)['file-preview'].FilePreviewSDK: (globalThis as any).FilePreview;// globalThis 在浏览器环境中会将模块挂载到全局对象(如 window)上,属性名就是在打包配置中指定的 name
// UMD 模块的全局变量名

传统浏览器用户:希望通过<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.pswp.cn/news/907937.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用NMEA Tools生成GPS轨迹图

前言 在现代科技快速发展的时代&#xff0c;GPS定位技术已经广泛应用于各种领域&#xff0c;如导航、物流、运动追踪等。为了获取和分析GPS数据&#xff0c;我们常常需要使用一些专业的工具。本文将详细介绍如何使用一款名为“NMEA Tools”的APK应用&#xff0c;结合GPSVisual…

Word双栏英文论文排版攻略

word写双栏英文论文的注意事项 排版首先改字体添加连字符还没完呢有时候设置了两端对齐会出现这样的情况&#xff1a; 公式文献 等我下学期有时间了&#xff0c;一定要学习Latex啊&#xff0c;word写英文论文&#xff0c;不论是排版还是公式都很麻烦的&#xff0c;而Latex一键就…

FactoryBean 接口

Spring 框架中 FactoryBean 接口的特性&#xff0c;这是 Spring 提供的一种特殊机制&#xff0c;用于创建和管理复杂 Bean。让我通过示例和解释帮您理解这个概念。 一、FactoryBean 是什么&#xff1f; FactoryBean 是 Spring 框架提供的一个工厂接口&#xff0c;用于创建复杂…

MySQL + CloudCanal + Iceberg + StarRocks 构建全栈数据服务

简述 在业务数据快速膨胀的今天&#xff0c;企业对 低成本存储 与 实时查询分析能力 的需求愈发迫切。 本文将带你实战构建一条 MySQL 到 Iceberg 的数据链路&#xff0c;借助 CloudCanal 快速完成数据迁移与同步&#xff0c;并使用 StarRocks 完成数据查询等操作&#xff0c…

javaEE->多线程:线程池

线程池 线程诞生的意义&#xff1a;因为进程的创建/销毁&#xff0c;太重量了&#xff08;比较慢&#xff09; 但如果近一步提高创建/销毁的频率&#xff0c;线程的开销也不容忽视。 有两种方法可以提高效率&#xff1a; 1.协程&#xff08;轻量级线程&#xff09;&#xff…

【001】利用github搭建静态网站_essay

文章目录 1. 简介2. 先了解网址规则2.1 文件及网址形式2.2 相互访问 3. 搭建网页的过程3.1 网页文件3.2 github搭建仓库及文件上传3.3 搭建网站 1. 简介 相信大家都有过想要自己搭建一个稳定可靠的网站&#xff0c;github是一个不错的选择&#xff0c;本来国内有gitee可以搭建…

搜索引擎2.0(based elasticsearch6.8)设计与实现细节(完整版)

1 简介 1.1 背景 《搜索引擎onesearch 1.0-设计与实现.docx》介绍了1.0特性&#xff0c;搜索schema&#xff0c;agg&#xff0c;表达式搜索映射&#xff0c;本文介绍onesearch 2.0 新特性, 参考第2节 规划特性与发布计划 1.2 关键词 文档 Document elasticsearch 一行数据称为…

【ClickHouse】RollingBitmap

ClickHouse 的 RollingBitmap 是一种基于 Bitmap 的数据结构&#xff0c;用于高效处理数据的动态变化和时间窗口计算。以下是关于 ClickHouse RollingBitmap 的相关信息&#xff1a; RollingBitmap 的应用场景 RollingBitmap 主要用于需要处理动态数据和时间窗口计算的场景&am…

新版智慧社区(小区)智能化弱电系统解决方案

该方案聚焦新版智慧社区智能化弱电系统建设,以物联网、云计算、AI 人脸识别等技术为支撑,构建涵盖智能可视化对讲、智慧门禁、智能梯控、智慧停车、视频监控等核心系统的社区智能化体系,并通过智慧社区集成平台实现设备管理、数据统计、预警联动等功能。方案旨在解决传统社区…

11高可用与容错

一、Broker 高可用架构设计 1.1 RabbitMQ 镜像集群方案 集群搭建步骤 # 节点1初始化 rabbitmq-server -detached rabbitmq-plugins enable rabbitmq_management# 节点2加入集群 rabbitmqctl stop_app rabbitmqctl join_cluster rabbitnode1 rabbitmqctl start_app# 创建镜像…

AsyncIOScheduler与BackgroundScheduler的线程模型对比

1. BackgroundScheduler的线程机制‌ ‌多线程模型‌&#xff1a;BackgroundScheduler基于线程池执行任务&#xff0c;默认通过ThreadPoolExecutor创建独立线程处理任务&#xff0c;每个任务运行在单独的线程中&#xff0c;主线程不会被阻塞。‌适用场景‌&#xff1a;适合同步…

ceph 对象存储用户限额满导致无法上传文件

查看日志 kl logs -f rook-ceph-rgw-my-store-a-5cc4c4d5b5-26n6j|grep -i error|head -1Defaulted container "rgw" out of: rgw, log-collector, chown-container-data-dir (init) debug 2025-05-30T19:44:11.573+0000 7fa7b7a6d700

2025-05-31 Python深度学习9——网络模型的加载与保存

文章目录 1 使用现有网络2 修改网络结构2.1 添加新层2.2 替换现有层 3 保存网络模型3.1 完整保存3.2 参数保存&#xff08;推荐&#xff09; 4 加载网络模型4.1 加载完整模型文件4.2 加载参数文件 5 Checkpoint5.1 保存 Checkpoint5.2 加载 Checkpoint 本文环境&#xff1a; Py…

批量导出CAD属性块信息生成到excel——CAD C#二次开发(插件实现)

本插件可实现批量导出文件夹内大量dwg文件的指定块名的属性信息到excel&#xff0c;效果如下&#xff1a; 插件界面&#xff1a; dll插件如下&#xff1a; 使用方法&#xff1a; 1、获取此dll插件。 2、cad命令行输入netload &#xff0c;加载此dll&#xff08;要求AutoCAD&…

在Linux环境里面,Python调用C#写的动态库,如何实现?

在Linux环境中&#xff0c;Python可以通过pythonnet&#xff08;CLR的Python绑定&#xff09;或subprocess调用C#动态库。以下是两种方法的示例&#xff1a; 方法1&#xff1a;使用pythonnet&#xff08;推荐&#xff09; 前提条件 安装Mono或.NET Core运行时安装pythonnet包…

小程序跳转H5或者其他小程序

1. h5跳转小程序有两种情况 &#xff08;1&#xff09;从普通浏览器打开的h5页面跳转小程序使用wx-open-launch-weapp可以实现h5跳转小程序 <wx-open-launch-weappstyle"display:block;"v-elseid"launch-btn":username"wechatYsAppid":path…

性能优化 - 案例篇:缓冲区

文章目录 Pre1. 引言2. 缓冲概念与类比3. Java I/O 中的缓冲实现3.1 FileReader vs BufferedReader&#xff1a;装饰者模式设计3.2 BufferedInputStream 源码剖析3.2.1 缓冲区大小的权衡与默认值 4. 异步日志中的缓冲&#xff1a;Logback 异步日志原理与配置要点4.1 Logback 异…

文档整合自动化

主要功能是按照JSON文件&#xff08;Sort.json&#xff09;中指定的顺序合并多个Word文档&#xff08;.docx&#xff09;&#xff0c;并清除文档中的所有超链接。最终输出合并后的文档名为"sorted_按章节顺序.docx"。 主要分为几个部分&#xff1a; 初始化配置 定…

嵌入式(C语言篇)Day13

嵌入式Day13 一段话总结 文档主要介绍带有头指针和尾指针的单链表的实现及操作&#xff0c;涵盖创建、销毁、头插、尾插、按索引/数据增删查、遍历等核心操作&#xff0c;强调头插/尾插时间复杂度为O(1)&#xff0c;按索引/数据操作需遍历链表、时间复杂度为O(n)&#xff0c;并…

【ASR】基于分块非自回归模型的流式端到端语音识别

论文地址:https://arxiv.org/abs/2107.09428 摘要 非自回归 (NAR) 模型在语音处理中越来越受到关注。 凭借最新的基于注意力的自动语音识别 (ASR) 结构,与自回归 (AR) 模型相比,NAR 可以在仅精度略有下降的情况下实现有前景的实时因子 (RTF) 提升。 然而,识别推理需要等待…