一文搞懂 JavaScript 模块化

💡 原文中文,约6100字,阅读约需15分钟。
📝

内容提要

本文介绍了JavaScript中的主要模块方案,包括CommonJS、AMD、UMD和ESM,并解释了它们的背景和使用方式。同时还介绍了打包工具如何支持ESM以及如何解决Node.js和浏览器之间的差异问题。最后提到了双包问题和Deno对模块加载的不同方式。

🔎

延伸解读

模块化演进:从手动依赖到标准规范

JavaScript 早期没有模块系统,开发者需手动管理脚本引入顺序,容易导致依赖混乱。Node.js 的 CommonJS 解决了服务端模块化问题,而浏览器社区推出 AMD 支持异步加载。两者并存造成社区割裂,UMD 的出现暂时统一了多种环境。2015 年 ESM 成为语言原生标准,逐渐成为主流。理解这段历史有助于看清现代工具链的设计逻辑。

打包工具与浏览器兼容策略

ESM 虽为原生标准,但早期缺乏运行环境支持,Rollup 和 Webpack 等打包工具通过配置字段(如 module、browser)来区分不同入口。浏览器则通过 script type="module" 原生加载 ESM。这些方案让库能同时服务于 Node.js 和浏览器,但需注意 package.json 中字段的优先级和工具差异,避免引入错误版本。

Node.js 支持 ESM 的挑战与双包问题

Node.js 从 18 版本开始稳定支持 ESM,但需兼容大量 CommonJS 模块,因此通过 .mjs、.cjs 后缀或 package.json 的 type 字段区分。exports 字段提供了更精细的入口控制。然而,若同时提供 ESM 和 CommonJS 入口,可能因依赖树中混用而导致代码执行两遍,即双包问题。对于有状态或单例需求的包,需采用包装或仅提供 ESM 等策略规避。

Deno 的模块加载差异与未来趋势

Deno 只支持通过 URL 加载模块,与 Node.js 的文件路径方式不同。若要在 Deno 中使用 npm 包,需借助 unpkg 等平台,并在 package.json 中添加相应字段。值得注意的是,Node.js 17 以后也支持通过 URL 加载 ESM,这反映了模块加载方式正在趋向统一。了解这些差异有助于在跨运行时项目中做出合适的技术选型。

❓

Q&A

JavaScript模块化的历史背景是什么?

JavaScript早期没有模块系统,依赖手动管理引入顺序,导致封装和依赖管理问题。

CommonJS和AMD的主要区别是什么?

CommonJS是为同步导入设计,适用于Node.js,而AMD是为异步加载设计,适用于浏览器。

UMD规范是如何解决模块兼容性问题的?

UMD规范支持多种模块环境,允许在传统JavaScript、AMD和CommonJS中使用,解决了社区割裂问题。

ESM是什么,它与CommonJS的引用方式有什么不同?

ESM是ECMAScript6引入的原生模块系统,引用方式使用import语法,而CommonJS使用require。

如何通过打包工具支持ESM?

打包工具如Rollup和Webpack支持ESM,通过配置module字段和mainFields实现兼容。

Node.js如何处理ESM与CommonJS的兼容性问题?

Node.js通过后缀名和package.json中的type字段区分ESM和CommonJS,同时支持两者的引用。

🏷️

标签

➡️

继续阅读