ESM 和 CommonJS 的差异和互操作性

我们已经提到了 ESM 和 CommonJS 之间的几个重要区别,例如必须在使用 ESM 导入时显式指定文件扩展名,而文件扩展名对于 CommonJS require 函数来说是完全可选的。

让我们通过讨论 ESM 和 CommonJS 之间的其他一些重要区别以及这两个模块系统如何在必要时协同工作来结束本章。

ESM 在严格模式下运行

ES 模块在严格模式下隐式运行。 这意味着我们不必在每个文件的开头显式添加 “use strict” 语句。 严格模式无法禁用; 因此,我们不能使用未声明的变量或 with 语句或具有其他仅在非严格模式下可用的功能,但这绝对是一件好事,因为严格模式是一种更安全的执行模式。

如果您想了解更多有关两种模式之间差异的信息,可以查看 MDN Web Docs 上一篇非常详细的文章 ( https://nodejsdp.link/strict-mode )。

ESM 中缺少引用

在 ESM 中,一些重要的 CommonJS 引用没有定义。 其中包括 requireexportsmodule.exports__filename__dirname。 如果我们尝试在 ES 模块中使用它们中的任何一个,因为它也在严格模式下运行,我们将得到一个 ReferenceError:

console.log(exports) // ReferenceError: exports is not defined
console.log(module) // ReferenceError: module is not defined
console.log(__filename) // ReferenceError: __filename is not defined
console.log(__dirname) // ReferenceError: __dirname is not defined

我们已经详细讨论了 CommonJS 中导出和模块的含义; __filename__dirname 表示当前模块文件的绝对路径及其父文件夹的绝对路径。 当我们需要构建相对于当前文件的路径时,这些特殊变量非常有用。

在 ESM 中,可以通过使用特殊对象 import.meta 来获取对当前文件 URL 的引用。 具体来说,import.meta.url 是对当前模块文件的引用,格式类似于 file:///path/to/current_module.js。 该值可用于以绝对路径的形式重建 __filename__dirname

import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

也可以重新创建 require() 函数,如下所示:

import { createRequire } from 'module'
const require = createRequire(import.meta.url)

现在我们可以使用 require() 在 ES 模块的上下文中导入来自 CommonJS 模块的功能。

另一个有趣的区别是 this 关键字的行为。

在 ES 模块的全局作用域中,这是 undefined,而在 CommonJS 中,这是对 exports 的引用:

// this.js - ESM
console.log(this) // undefined

// this.cjs – CommonJS
console.log(this === exports) // true

互操作性

我们在上一节中讨论了如何使用 module.createRequire 函数在 ESM 中导入 CommonJS 模块。 还可以使用标准导入语法从 ESM 导入 CommonJS 模块。 但这仅限于默认导出:

import packageMain from 'commonjs-package' // Works
import { method } from 'commonjs-package' // Errors

不幸的是,无法从 CommonJS 模块导入 ES 模块。

此外,ESM 无法直接将 JSON 文件作为模块导入,这是 CommonJS 中经常使用的功能。 以下导入语句将失败:

import data from './data.json'

它将产生 TypeError(未知文件扩展名:.json)。

为了克服这个限制,我们可以再次使用 module.createRequire 实用程序:

import { createRequire } from 'module'
const require = createRequire(import.meta.url)
const data = require('./data.json')
console.log(data)

即使在 ESM 中,我们也正在进行原生支持 JSON 模块的工作,因此在不久的将来我们可能不需要依赖 createRequire() 来实现此功能。