CommonJS 正在损害 JavaScript

272 阅读5分钟

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

CommonJS 的兴起

JavaScript 发明大约 15 年后,开始从浏览器扩展到服务器。更大的项目正在使用该语言构建,而 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.js 的一部分。

建议的 CommonJS 语法(require()module.exports等)看起来不像客户端 JavaScript。这是设计使然。Dangoor 想要将 CommonJS 与浏览器 JavaScript 区分开来,这一点从他2009 年在 CommonJS Google Group上的消息中就可以看出来 :

我确实认为服务器端代码的需求与客户端代码的需求有很大不同,因此我们最好从 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-shake,这可以删除未使用的模块并最小化包大小。
  • 不是浏览器原生的。您需要捆绑器和转译器来使所有这些代码在客户端工作。使用 CommonJS,您要么陷入 巨大的构建步骤,要么为客户端和服务器编写单独的代码。

到 2013 年,CommonJS 小组开始逐渐解体。但到了那一年,负责监督 JavaScript 核心语言更新的 TC39 委员会已经开始开发 CommonJS 模块的后继者:ECMAScript 模块。

ECMAScript 模块是网络优先的

通过ES6 语言规范,TC39 委员会最终引入了直接内置于 JavaScript 语言中的模块系统。目标是构建一个适合Web的单模块加载器系统,包括异步模块加载、与浏览器的兼容性、静态分析和Tree Shaking。

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

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

Anakin Padme 4 关于 ESM 与 CommonJS 的小组模因

…正确的?

Node 决定同时支持 CJS 和 ESM

ES 模块和 Common JS 的结合就像老海湾调味料和香草冰淇淋一样

— Myles Borins,来自模块模块的演讲

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

Borins 是 Node“模块团队”的开发人员之一,负责在 Node.js 中实现 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 是更好的解决方案,因为他们可以编写与浏览器兼容的代码,对于获得更好最终体验的用户来说也是如此。