跳至主要内容
Deno 2 终于来了 🎉️
了解更多
Progress of JavaScript being obstructed by CommonJS.

CommonJS 正在损害 JavaScript

JavaScript,无可争议的 Web 开发之王,正在被破坏 - 不是被竞争对手的语言或革命性的新技术破坏,而是被它自身过去遗留下来的包袱所破坏。这个阴险的破坏者就是 CommonJS,我们容忍了太久了的古老模块系统。

CommonJS 的崛起

在发明大约 15 年后,JavaScript 开始超越浏览器,扩展到服务器端。更大的项目正在使用这种语言构建,JavaScript 需要一种更好的方法来处理大量的源代码。它需要模块化。

2009 年,Mozilla 开发人员 Kevin Dangoor 发出号召。在 “服务器端 JavaScript 需要什么” 中,他阐述了新兴的服务器端 JS 领域缺少许多东西,包括一个模块系统。

JavaScript 需要一种标准方式来包含其他模块,并使这些模块存在于独立的命名空间中。有一些简单的命名空间方法,但没有标准的编程方式来加载模块(一次!)。这非常重要,因为服务器端应用程序可以包含大量代码,并且可能会混合和匹配符合这些标准接口的各个部分。

— Kevin Dangoor,服务器端 JavaScript 需要什么(2009)

在一周内,224 人加入了当时被称为 ServerJS Google 群组,其中包括 npm 创始人 Issac Schlueter 和 Node.js 创建者 Ryan Dahl(他在这里向该群组介绍了 Node)。这个邮件列表将继续规范出 CommonJS 的第一个版本,这个模块系统成为了 Node 的一部分。

提议的 CommonJS 语法(require()module.exports 等)与客户端 JavaScript 不一样。这是有意的。Dangoor 想要将 CommonJS 与浏览器 JavaScript 区分开来的意图在他 2009 年在 CommonJS Google 群组中的消息 中表露无遗

我真的认为,服务器端代码的需求与客户端代码的需求足够不同,以至于我们最好从 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 存在一些核心问题

  • 模块加载是同步的。每个模块都是一个接一个地加载和执行,按照它们被引用的顺序。
  • 难以进行树状摇动,这可以删除未使用的模块并最小化捆绑包大小。
  • 不是浏览器原生。您需要捆绑器和转译器才能使所有这些代码在客户端工作。使用 CommonJS,您只能选择 大型构建步骤 或者为客户端和服务器编写单独的代码。

到 2013 年,CommonJS 团队开始逐渐解散。但就在那一年,负责监督核心 JavaScript 语言更新的 TC39 委员会已经在着手 CommonJS 模块的继任者:ECMAScript 模块。

ECMAScript 模块是 Web 为先的

随着 ES6 语言规范 的发布,TC39 委员会终于在 JavaScript 语言中引入了内置的模块系统。目标是构建一个适用于 Web 的单一模块加载器系统,其中包括异步模块加载、与浏览器的兼容性、静态分析和树状摇动。

ES 模块假设它们将通过网络而不是在文件系统中获取数据,从而提供更好的性能和用户体验。

现在,模块加载器系统已内置于语言中,每个人都会同意使用它,这样我们就可以将精力集中在更高级、更重要的问题上,对吗?

Anakin Padme 4 panel meme about ESM vs. CommonJS

…对吗?

Node 决定同时支持 CJS 和 ESM

ES 模块和 Common JS 就像老湾调味料和香草冰淇淋一样搭配在一起

— Myles Borins,摘自关于 模块 模块 模块 的演讲

(我来自马里兰州,所以这对我来说听起来很棒。)

Borins 是 Node ‘模块团队’ 中的一名开发人员,负责在 Node 中实现 ES 模块。尽管成功地将 ESM 添加到 Node 中,但该团队无法就 ESM 与 CJS 之间的互操作性达成明确的共识。然而,Node 无法删除 CJS,因为它已经深深嵌入其中。这意味着互操作性问题被推给了包作者。

以下是一个模块的 package.json 代码片段,需要支持 ESM 和 CJS

发布支持 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 上关注我们