苗木企业网站建设源代码,做网站端口映射,手机网站建设域名空间,传统网站 手机网站文章目录 前言一、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 如何处… 文章目录 前言一、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 如何处理 AMD2.4 适用场景 三、UMD (Universal Module Definition) - 兼容浏览器 Node.js 的缝合怪3.1 设计背景3.2 为什么 UMD 代码看起来这么丑3.3 构建工具如何生成 UMD3.4 适用场景 四、ES Modules (ESM) - 现代 JavaScript 标准4.1 设计背景4.2 为什么旧浏览器不支持 ESM4.3 构建工具如何处理 ESM4.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 srcrequire.js/script
scriptrequire([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 /* 模块内容 */;
});关键转换策略
包裹 IIFEImmediately Invoked Function Expression立即调用函数表达式避免污染全局作用域注入环境判断运行时动态选择模块系统
3.4 适用场景
开源库开发如 Lodash、Moment.js需要同时支持script标签和 npm 安装的项目
export function kInstallScript(src: string): Promisevoid {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 srcawesome-lib.js直接使用 现代前端工程希望通过 npm install awesome-lib 引入 用户环境不可控而 UMD 就是最好的兼容方案 当使用 RollupWebpack 之类的打包器时UMD 通常用作备用模块 四、ES Modules (ESM) - 现代 JavaScript 标准
4.1 设计背景
目标环境现代浏览器 Node.jsES6核心需求官方标准、静态分析、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 typemoduleimport { add } from ./math.js; // 正常工作
/script
!-- 旧浏览器 --
scriptimport { 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 3Node.js 14 后端项目
五、模块系统对比总结
模块系统加载方式环境支持构建工具转换策略典型应用场景CJS同步Node.js包裹为函数模拟 requireNode.js 后端AMD异步浏览器 (RequireJS)转为 Promise 代码拆分旧版浏览器 SPAUMD兼容多种浏览器 Node.jsIIFE 环境嗅探开源库开发ESM静态现代浏览器 Node原生支持或转为 CJS现代前端/Node 项目
特性ESMAMD/RequireJSCommonJS依赖获取时机并行请求所有发现的依赖按需并行请求同步阻塞加载执行触发条件整个依赖图就绪后拓扑序执行串行回调按照申明顺序遇到 require 时立即执行循环依赖处理语言标准明确定义引用未初始化值依赖加载器实现可能不一致部分导出可能为 undefined典型场景import ./module.jsrequire([module], callback)const m require(./module) 总结
CJSNode.js 专用同步加载需构建工具转换才能在浏览器运行AMD旧浏览器异步加载方案已被 ESM 取代UMD兼容浏览器 Node.js 的过渡方案适合库开发ESM现代标准支持 Tree Shaking未来唯一选择 构建工具的作用就是抹平环境差异让开发者可以用任意模块规范编写代码最终输出目标环境可运行的版本。