CommonJS 正在损害 JavaScript
JavaScript 是无可争议的 Web 开发之王,但它正在被破坏——不是被竞争对手语言或革命性的新技术,而是被其自身的历史包袱所拖累。这个阴险的破坏者不是别人,正是 CommonJS,这个我们容忍了太久的古老模块系统。
CommonJS 的兴起
在 JavaScript 发明大约 15 年后,它开始从浏览器扩展到服务器。使用该语言构建了更大的项目,JavaScript 需要一种更好的方法来处理大量源代码。它需要模块化。
2009 年,Mozilla 开发者 Kevin Dangoor 发出了战斗号召。在 “服务端 JavaScript 需要什么” 中,他阐述了新兴的服务端 JS 领域缺少的许多东西,包括模块系统。
JavaScript 需要一种标准方法来包含其他模块,并让这些模块存在于离散的命名空间中。有很多简单的方法可以实现命名空间,但没有标准的编程方式来加载模块(一次!)。这非常重要,因为服务端应用程序可以包含大量代码,并且很可能会混合搭配符合这些标准接口的部分。
— Kevin Dangoor, 服务端 JavaScript 需要什么 (2009)
在一个星期内,包括 npm 创始人 Issac Schlueter 和 Node.js 创建者 Ryan Dahl 在内的 224 人加入了当时名为 ServerJS google group 的小组(在这里他向小组介绍了 Node)。这个邮件列表将继续制定 CommonJS 的第一个版本,该模块系统成为 Node 的一部分。
提议的 CommonJS 语法(require()
、module.exports
等)看起来不像客户端 JavaScript。这是有意为之的。Dangoor 在 2009 年 CommonJS Google Group 中的消息明确表示,他打算将 CommonJS 与浏览器 JavaScript 区分开来
我真的认为服务端代码的需求与客户端代码的需求大相径庭,以至于我们最好从 Python 和 Ruby 中汲取经验,而不是从 Dojo 和 jQuery 中汲取经验。
除了 Node.js 之外,其他几个早期的服务端 JavaScript 运行时也采用了 CommonJS,例如 Flusspferd、GPSEE、Narwhal、Persevere、RingoJS、Sproutcore 和 v8cgi(大多数由核心 CommonJS 小组构建)。
但是,随着 Node.js 成为事实上的服务端 JavaScript 运行时,CommonJS 成为其主要的模块系统,更广泛的 CommonJS 标准化工作失去了动力。当只有一个主要的运行时:Node.js 实现成为标准时,对标准的需求减少了。
现在回想起来,在我看来,CommonJS 的目标是(或者至少应该一直是)发现 Node,并启用我们在这里构建的东西。犯了一些错误,因为事后诸葛亮并不能如你所愿,但总的来说,我认为整个 CommonJS 项目可以被认为是成功的。
— Issac Schlueter,评论 打破 CommonJS 标准化僵局 (2013)
尽管 CommonJS 是默认的模块系统,但它存在一些核心问题
- 模块加载是同步的。每个模块都按需加载和执行,一个接一个,按照它们被要求的顺序。
- 难以进行 tree-shaking,tree-shaking 可以移除未使用的模块并最大限度地减小捆绑包大小。
- 不是浏览器原生。您需要捆绑器和转译器才能使所有这些代码在客户端工作。使用 CommonJS,您要么陷入 大型构建步骤,要么为客户端和服务器编写单独的代码。
到 2013 年,CommonJS 小组开始解散。但到那一年,负责监督核心 JavaScript 语言更新的 TC39 委员会已经在研究 CommonJS 模块的继任者:ECMAScript 模块。
ECMAScript 模块是 Web 优先的
通过 ES6 语言规范,TC39 委员会最终引入了一个内置于 JavaScript 语言的模块系统。目标是构建一个适用于 Web 的单一模块加载器系统,其中包括异步模块加载、与浏览器的兼容性、静态分析和 tree shaking。
ES 模块假设它们将通过网络而不是文件系统获取数据,从而提供更好的性能和用户体验。
既然模块加载系统已内置到语言中,那么每个人都会同意使用它,这样我们就可以将精力集中在更高级、更重要的问题上,对吧?
…对吧?
Node 决定同时支持 CJS 和 ESM
ES 模块和 CommonJS 就像老海湾调味料和香草冰淇淋一样格格不入
— Myles Borins,在关于 模块 模块 模块 的演讲中
(我来自马里兰州,所以这听起来很棒。)
Borins 是 Node '模块团队' 的开发人员之一,该团队负责在 Node 中实现 ES 模块。尽管团队成功地将 ESM 添加到 Node 中,但在 ESM 和 CJS 之间的互操作性方面,团队未能达成明确的共识。然而,Node 无法完全移除 CJS,因为它已经深入嵌入。这意味着互操作性问题被推给了包作者。
以下是模块的 package.json
代码片段,需要同时支持 ESM 和 CJS
“发布在 esm 和 cjs 中工作的包简直是一场噩梦” — Wes Bos
其他模块作者发现使用 dnt 支持 CommonJS 和 ESM 取得了成功。只需用 TypeScript 编写您的模块,这个构建工具就会将其转换为 Node.js,发出 ESM/CommonJS/TypeScript 声明文件和一个 package.json
。
很明显,在 2023 年支持 CommonJS 已经成为一个不容忽视的大问题。现在是我们埋葬 CommonJS 并过渡到全 ESM 未来的时候了。
require
再见,感谢所有的 我们设想一个未来,在安装模块后,开发人员将能够在 Node.js 或浏览器中运行代码,而无需构建步骤。
— Myles Borins, ESModules 的当前实施状态和规划 (2017)
在 2009 年,CommonJS 正是 JavaScript 所需要的。该小组解决了一个棘手的问题,并强制实施了一个解决方案,该解决方案每天仍被使用数百万次。
但是,随着 ESM 成为标准,并且重点转向云原生——边缘、浏览器和无服务器计算——CommonJS 根本无法胜任。ESM 对于开发人员来说是更好的解决方案,因为他们可以编写符合浏览器的代码——对于用户来说,他们可以获得更好的最终体验。
不要错过任何更新 — 在 Twitter 上关注我们。