概念
ECMAScript 模块(ESM)和 CommonJS 是两种不同的模块系统,它们分别用于定义如何在 JavaScript 中组织代码、导入和导出模块。
CommonJS
背景
- CommonJS 最初是为服务器端 JavaScript 环境设计的,特别是 Node.js
- 它在 Node.js 中广泛使用,并且在早期阶段成为 Node.js 的默认模块系统
特点
- 同步加载:CommonJS 模块是在运行时同步加载的,这意味着模块的依赖关系需要在模块执行前完全解析
- 导入导出:使用
require 导入,module.exports 或 exports 导出,exports 是对 module.exports 的引用,区别在于不能 exports = ,只能 module.exports =
- 全局变量:在每个模块中,默认提供了一些全局变量,如
__dirname 和 __filename,表示当前模块所在的目录和文件名ECMAScript 模块(ESM)
背景
- ESM 是由 ECMAScript 标准定义的模块系统,旨在为浏览器和服务器端 JavaScript 提供统一的模块化方案
- 它从 ES6(ES2015)开始引入,并逐渐被现代 JavaScript 运行环境支持
特点
- 异步加载:ESM 支持异步加载模块,这对于浏览器环境尤为重要,因为它允许模块按需加载,提高性能
- 导入导出:使用
import 导入,export 导出
- 全局变量:
import.meta 提供有关当前模块的元数据,如模块的 URL
Node.js 环境已经开始支持 ESM 模块系统,但由于很多第三方库还没有进行更新,就出现了 ESM 和 CommonJS 共存的现象。有的库只支持 ESM,有的只支持 CommonJS,还有的两者都支持。这就需要注意两个模块在互相导入时需使用正确的方式。
相互引用
在 CommonJS 中引用 ESM 模块
在 Node.js 项目中都会有一个 package.json 文件,其中的 type 字段指定了项目使用的模块系统,默认是 commonjs。
因此,默认情况下,项目中所有的 .js 文件都会被视为 CommonJS 模块。若在文件中使用 ESM 的导入导出就会报错。
如果要使用 ESM 模块,需要将文件命名为 .mjs,表示该文件一个 ESM 模块。
在 CommonJS 中引用 ESM 模块只能使用动态导入 import()。
默认导出/导入
1 2 3 4 5 6 7
|
function f () { console.log("hello world"); }
export default f;
|
1 2 3 4 5
|
import("./hello.mjs").then(module => { module.default(); })
|
具名导出/导入
1 2 3 4 5 6 7
|
function f () { console.log("hello world"); }
export { f };
|
1 2 3 4 5
|
import("./hello.mjs").then(module => { module.f(); })
|
在 ESM 中引用 CommonJS 模块
要开启 ESM,需要将 package.json 中的 type 设为 module。
这时项目中所有的 .js 文件都会被视为 CommonJS 模块。
如果要使用 CommonJS 模块,需要将文件命名为 .cjs,表示该文件一个 CommonJS 模块。
默认导出/导入
1 2 3 4 5 6 7
|
function f () { console.log("hello world"); }
module.exports = f;
|
1 2 3 4 5
|
import f from "hello.mjs";
f()
|
具名导出/导入
1 2 3 4 5 6 7
|
function f () { console.log("hello world"); }
exports.f = f;
|
1 2 3 4 5
|
import { f } = from "hello.mjs";
f()
|
注意:CommonJS 模块是没有默认导出和具名导出的概念的,因为 module.exports 或 exports 是一个对象,所谓具名导出不过是导出在 exports 对象上添加的属性罢了,而默认导出就是导出整个 exports 对象。
1 2 3 4 5 6 7
| function f () { console.log("hello world"); }
exports.x = 1; exports.y = 2; module.exports = f;
|
以上代码最后导出的是 f,因为 module.exports = f 重新赋值,exports.x 和 exports.y 被覆盖了。
1 2 3 4 5 6 7
| function f () { console.log("hello world"); }
module.exports = f; exports.x = 1; exports.y = 2;
|
这样倒是可以模仿同时存在具名导出和默认导出,但本质上是将 x 和 y 挂载到了 f 上,若 f 不是一个对象,那 x 和 y 依然不能被导出。