JavaScript 和 Node.js 中的模块系统
并非所有编程语言都有内置模块系统,JavaScript 长期以来一直缺乏这一功能。
在浏览器环境中,可以将代码库分割成多个文件,然后使用不同的 <script> 标记导入它们。多年来,这种方法足以构建简单的交互式网站,而 JavaScript 开发人员也能在没有成熟模块系统的情况下完成工作。
直到 JavaScript 浏览器应用程序变得越来越复杂,jQuery、Backbone 和 AngularJS 等框架接管了整个生态系统,JavaScript 社区才提出了几项倡议,旨在定义一个可在 JavaScript 项目中有效采用的模块系统。其中最成功的是由 RequireJS(nodejsdp.link/requirejs)推广的异步模块定义(AMD),以及后来的通用模块定义(UMD - nodejsdp.link/umd)。
当 Node.js 诞生时,它被设想为 JavaScript 的服务器运行时,可以直接访问底层文件系统,因此有一个独特的机会来引入一种不同的方式来管理模块。我们的想法是不依赖 HTML <script> 标记和通过 URL 访问的资源。取而代之的是纯粹依赖本地文件系统中的 JavaScript 文件。在模块系统方面,Node.js 采用了 CommonJS 规范(有时也称为 CJS,nodejsdp.link/commonjs),该规范旨在为无浏览器环境中的 JavaScript 提供模块系统。
自 Node.js 诞生以来,CommonJS 一直是 Node.js 中占主导地位的模块系统,由于 Browserify(nodejsdp.link/browserify)和 webpack(nodejsdp.link/ webpack)等模块捆绑程序的出现,它在浏览器领域也变得非常突出。
2015 年,随着 ECMAScript 6(也称作 ECMAScript 2015 或 ES2015)的发布,官方终于提出了标准模块系统的建议: ESM 或 ECMAScript 模块。ESM 为 JavaScript 生态系统带来了许多创新,其中包括尝试弥合浏览器和服务器上模块管理方式之间的差距。
ECMAScript 6 仅在语法和语义方面定义了 ESM 的正式规范,但没有提供任何实现细节。不同的浏览器公司和 Node.js 社区花了数年时间才提出了该规范的可靠实现方法。Node.js 从 13.2 版开始提供对 ESM 的稳定支持。
在撰写本文时,人们普遍认为 ESM 将成为在浏览器和服务器环境中管理 JavaScript 模块的实际方法。但目前的现实情况是,大多数项目仍然严重依赖 CommonJS,ESM 要想迎头赶上并最终成为主流标准还需要一些时间。
为了全面介绍 Node.js 中与模块相关的模式,在本章的第一部分,我们将在 CommonJS 的上下文中讨论这些模式,然后在本章的第二部分,我们将使用 ESM 重温我们的学习成果。
本章的目标是让你对这两种模块系统都能得心应手,但在本书的其余部分,我们将只在代码示例中使用 ESM。我们的目的是鼓励你尽可能多地使用 ESM,这样你的代码就能更好地面向未来。
如果你是在本章出版几年后阅读本章,你可能并不太担心 CommonJS,可能想直接跳到 ESM 部分。这可能没有问题,但我们仍然鼓励您通读全章,因为了解 CommonJS 及其特性肯定会有助于您更深入地理解 ESM 及其优势。