【译】- Node.js中的CommonJS vs. ES modules

2,456 阅读7分钟

学习链接

CommonJS vs. ES modules in Node.js - LogRocket Blog

译文

在现代软件开发中,模块将软件代码组织成独立的小块,共同构成一个更大、更复杂的应用程序


在浏览器的JavaScript生态系统中,JavaScript模块的使用取决于导入(import)和导出(export)语句;这些语句分别加载和导出EMCAScript模块(或ES模块)。

  • ES模块格式是官方的标准格式,用于打包JavaScript代码以便重用,大多数现代的网络浏览器都原生支持该模块

  • 然而,Node.js默认支持CommonJS模块格式。CommonJS模块使用require()加载,变量和函数使用module.exports从CommonJS模块导出。


ES模块格式是在Node.js v8.5.0中引入的,因为JavaScript模块系统已经标准化。作为一个实验性的模块,要在Node.js环境中成功运行ES模块,需要使用--实验性模块标志(--experimental-modules)

从13.2.0版本开始,Node.js对ES模块有了稳定的支持

这篇文章不会涉及两种模块格式的使用,而是介绍CommonJS与ES模块的比较,以及为什么你可能想使用其中一种而不是另一种。

比较CommonJS和ES的语法

CommonJS语法

默认情况下,Node.js将JavaScript代码视为CommonJS模块。正因为如此,CommonJS模块的特点:

  • 模块导入时使用require()语句
  • 模块导出时使用module.exports

例如,这是一个CommonJS模块,导出了两个函数。

image.png

我们也可以使用require()将公共函数导入另一个Node.js脚本

image.png


ES语法

另一方面,库作者也可以简单地在Node.js包中启用ES模块将文件扩展名从.js改为.mjs,导出两个函数供公共使用。

image.png

然后我们可以使用import语句导入这两个函数。

image.png

另一种在你的项目中启用ES模块的方法可以通过在最近的package.json文件(与你制作的包相同的文件夹)内添加一个 "类型:模块 " ("type: module" ) 字段来完成。

image.png

有了这条语句,Node.js就会将该包内的所有文件视为ES模块不必将文件的扩展名改为.mjs

另外,可以安装并设置一个像Babel这样的转译器,将ES模块语法编译成CommonJS语法

React和Vue这样的项目支持ES模块,因为它们在引擎盖下使用Babel来编译代码

Node.js中使用ES和CommonJS的优缺点

ES模块是JavaScript的标准,而CommonJS是Node.js的默认模块。

ES模块格式的创建是为了使JavaScript模块系统标准化。它已经成为封装JavaScript代码以便重用的标准格式。

CommonJS模块系统是内置于Node.js的。在Node.js中引入ES模块之前CommonJS是Node.js模块的标准。因此,有很多用CommonJS编写的Node.js库和模块

对于浏览器的支持,所有主要浏览器都支持ES模块语法,可以在React和Vue.js等框架中使用import/export。这些框架使用像Babel这样的转译器,将import/export 语法编译成require(),旧的Node.js版本原生支持这种语法。

除了作为JavaScript模块的标准,ES模块的语法与require()相比也更易读。由于语法相同,主要在客户端编写JavaScript的Web开发人员在使用Node.js模块时不会有任何问题

Node.js对ES模块的支持

旧的Node.js版本不支持ES模块

虽然ES模块已经成为JavaScript的标准模块格式,但开发者应该考虑到旧版本的Node.js缺乏支持(特别是Node.js v9及以下)。

换句话说,使用ES模块会使应用程序与只支持CommonJS模块(即require()语法)的早期版本的Node.js不兼容

但有了新的条件,我们可以构建双模块库。这些库是由较新的ES模块组成的,但它们也向后兼容旧的Node.js版本所支持的CommonJS模块格式。

换句话说,我们可以建立一个同时支持import和require()的库,解决不兼容的问题。 考虑以下Node.js项目

image.png

在package.json中,我们可以使用exports字段以两种不同的模块格式导出公共模块(模块a),同时限制对私有模块(模块b)的访问

image.png

通过提供以下关于我们的my-library包的信息,我们可以在任何支持它的地方使用它

image.png

由于出口中的路径,我们可以导入(和require())我们的公共模块,而不需要指定绝对路径

通过.js和.mjs的路径,我们可以解决不兼容的问题;可以为不同的环境如浏览器和Node.js映射包模块,同时限制对私有模块的访问。

较新的Node.js版本完全支持ES模块

在大多数较低的Node.js版本中,ES模块被标记为实验性。这意味着该模块缺乏一些功能,并且是在--实验性模块标志后面。新版本的Node.js确实对ES模块有稳定的支持

然而,重要的是要记住,为了让Node.js将一个模块视为ES模块,必须发生以下情况之一:

  1. 要么模块的文件扩展名必须从.js(对于CommonJS)转换为.mjs(对于ES模块)
  2. 或者我们必须在最近的package.json文件中设置{"type": "module"}字段

CommonJS模块导入的灵活性

在ES模块中,导入语句只能在文件的开头被调用在其他地方调用它,会自动将表达式转移到文件开头,甚至会出现错误

另一方面,将 require() 作为一个函数,在运行时得到解析。因此,require()可以在代码的任何地方被调用。你可以用它来有条件地或动态地从if语句、条件循环和函数中加载模块。例如:

image.png

在这里,我们只在有用户存在的情况下加载一个名为userDetails的模块

CommonJS同步加载,ES模块是异步的

使用 require() 的限制之一是它会同步加载模块。这意味着模块被逐一加载和处理

正如你可能已经猜到的,这可能会给拥有数百个模块的大规模应用程序带来一些性能问题在这种情况下,import可能比require()的异步行为更出色

然而,对于使用几个模块的小规模应用程序来说,require()的同步特性可能不是什么问题。

结论:使用CommonJS还是ES?

对于仍然使用旧版本的Node.js的开发者来说,使用新的ES模块是不切实际的。

由于支持不完善,将现有项目转换为ES模块会使应用程序与只支持CommonJS模块的早期版本的Node.js不兼容(即require()语法)。

因此,将你的项目迁移到使用ES模块可能不是特别有利。


作为一个初学者,学习ES模块是非常有益和方便的,因为它们正在成为在JavaScript中定义模块的标准格式,包括客户端(浏览器)和服务器端(Node.js)。

总而言之,EMCAScript模块是JavaScript的未来

总结

模块可以将代码分成独立的小块,以便共同组成一个更复杂的程序。在Javascript中,共有两种处理模块的方式:CommonJS和ES模块。

Node.js默认是CommonJS,早期版本采用CommonJS,后来版本有了对ES模块的支持,开启ES模块的方式主要有两种:

  • 修改文件名.mjs
  • 在package.json中添加"type":"module"

浏览器中主要支持ES模块,现已逐渐成为浏览器中的标准模块处理方式

模块的使用上也有不同

  • CommonJS的导入 - require(),导出 - module.exports.add
  • ES的导入 - import,导出 - export

模块的运行上也有不同

  • CommonJS可以在任何地方运行,以同步的方式在运行时得到解析,模块被逐一加载和处理,如果文件较大,会造成性能问题
  • ES仅可以用在文件开头,以异步的方式运行,如果不在开头引用,可以会报错。

文中还给出了利用package.json中的export,处理两种模块方式,可以同时支持import和require()。

长安道,人无衣,马无草,何不归来山中老。——顾况《横吹曲辞》