学习链接
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模块,导出了两个函数。
我们也可以使用require()将公共函数导入另一个Node.js脚本
ES语法
另一方面,库作者也可以简单地在Node.js包中启用ES模块,将文件扩展名从.js改为.mjs,导出两个函数供公共使用。
然后我们可以使用import语句导入这两个函数。
另一种在你的项目中启用ES模块的方法可以通过在最近的package.json文件(与你制作的包相同的文件夹)内添加一个 "类型:模块 " ("type: module" ) 字段来完成。
有了这条语句,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项目。
在package.json中,我们可以使用exports字段以两种不同的模块格式导出公共模块(模块a),同时限制对私有模块(模块b)的访问。
通过提供以下关于我们的my-library包的信息,我们可以在任何支持它的地方使用它。
由于出口中的路径,我们可以导入(和require())我们的公共模块,而不需要指定绝对路径。
通过.js和.mjs的路径,我们可以解决不兼容的问题;可以为不同的环境如浏览器和Node.js映射包模块,同时限制对私有模块的访问。
较新的Node.js版本完全支持ES模块
在大多数较低的Node.js版本中,ES模块被标记为实验性。这意味着该模块缺乏一些功能,并且是在--实验性模块标志后面。新版本的Node.js确实对ES模块有稳定的支持。
然而,重要的是要记住,为了让Node.js将一个模块视为ES模块,必须发生以下情况之一:
- 要么模块的文件扩展名必须从.js(对于CommonJS)转换为.mjs(对于ES模块),
- 或者我们必须在最近的package.json文件中设置{"type": "module"}字段。
CommonJS模块导入的灵活性
在ES模块中,导入语句只能在文件的开头被调用。在其他地方调用它,会自动将表达式转移到文件开头,甚至会出现错误。
另一方面,将 require() 作为一个函数,在运行时得到解析。因此,require()可以在代码的任何地方被调用。你可以用它来有条件地或动态地从if语句、条件循环和函数中加载模块。例如:
在这里,我们只在有用户存在的情况下加载一个名为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()。
长安道,人无衣,马无草,何不归来山中老。——顾况《横吹曲辞》