如何在 ESM 中使用 CJS 模块特有的变量和方法?

154 阅读3分钟

在 ES 模块的代码中,无法直接使用 require__dirname__filename ,否则会报错。

在 CommonJS 模块中:

  • require 用于加载模块

  • __dirname,CommonJS 模块的内置变量,表示当前模块的目录名。

  • __filename,CommonJS 模块的内置变量,表示当前模块的文件名。

接下来我们看看,如何在 ES 模块中使用 require 方法和拿到 __dirname__filename

require

在 ES 模块化的代码中,直接使用 require 方法加载 CommonJS 模块化的代码会报错:

ReferenceError: require is not defined in ES module scope

例如这个例子,此例子的目录结构如下:

56.png

common.cjs 文件为采用 CommonJS 模块化的代码

// common.cjs

function arrConcat(a, b) {
  return a.concat(b)
}
  
module.exports = {
  arrConcat
}

my-util.jsindex.js 为采用 ES 模块化的代码

// my-util.js
const isArr = (list) => {
  return Array.isArray(list)
}
  
export {
  isArr
}
// index.js
import { isArr } from './my-util.js'

console.log('isArr  ', isArr([1, 2, 3]))

const cmm = require('./common.cjs')

const cRes = cmm.arrConcat([1, 2], ['a', 'b'])
console.log('cRes  ', cRes)

👆 在 index.js 文件中,直接使用 require 引用 CommonJS 模块的代码。

package.json 文件则告知当前是 ES 模块化的环境:

{
  "type": "module"
}

然后在命令行终端使用 node 运行 index.js 文件,就发现报错了:

57.png

这个问题可以借助 Node.js 的 createRequire 方法解决。接着看下面的例子,此例子的目录结构与上面的例子一致,唯一的不同是 index.js 文件中使用 createRequire 方法创建 require 方法,并使用此 require 方法加载 common.cjs 文件:

// index.js
import { isArr } from './my-util.js'
import { createRequire } from 'module'

console.log('isArr  ', isArr([1, 2, 3]))
const require = createRequire(import.meta.url)
const cmm = require('./common.cjs')

const cRes = cmm.arrConcat([1, 2], ['a', 'b'])
console.log('cRes  ', cRes)

然后在终端使用 node 运行 index.js ,可以发现代码正常运行,没有报错了:

58.png

createRequire 方法接收一个文件 URL 对象、文件 URL 字符串或绝对路径字符串,返回 require 方法,可以使用该 require 方法在 ES 模块中加载 CommonJS 模块。

import.meta 是一个给 JavaScript 模块暴露特定上下文的元数据属性的对象。它包含了这个模块的信息,比如说这个模块的 URL 。

ESM 中引入 json

ES 模块不支持把 json 文件当成模块引用,而在 CommonJS 模块却可以。

在 ES 模块中,引入 json 文件会报错:

// index.js
import pkg from './package.json'

console.log('pkg  ', pkg)

package.json 文件如下:

{
  "type": "module"
}

然后在命令行终端运行 index.js 文件,报错如下:

59.png

要在 ES 模块中引入 json 文件,也可借助 createRequire 方法实现,通过createRequire 方法创建 require 方法,再使用该 require 方法引入 json 文件:

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

const pkg = require('./package.json')

console.log('pkg  ', pkg)

__dirname 和 __filename

在 CommonJS 中可以正常使用 __dirname__filename

// index.js
console.log('__dirname  ', __dirname)
console.log('__filename  ', __filename)

在命令终端使用 node 运行 index.js 文件:

60.png

而如果是在 ES 模块环境中,运行上面的代码,则会报错:

package.json 中指定 typemodule ,表示当前为 ES 模块化环境:

{
  "type": "module"
}

index.js 文件的内容同上面 CommonJS 中的一样,然后在命令终端使用 node 运行 index.js 文件,发现报错了:

61.png

在 ES 模块中,可以使用 import.meta.url ,拿到类似 __filename 的值:

console.log(import.meta.url)

执行后,会得到如下结果

file:///D:/work/code/demo/module-demo6/index.js

然后可通过 url 模块的 fileURLToPath 方法将 import.meta.url 转换为 __filename 的值

import { fileURLToPath } from 'url'

const __filename = fileURLToPath(import.meta.url)
console.log('__filename  ', __filename)
// __filename   D:\work\code\demo\module-demo6\index.js

url.fileURLToPath(url) 方法用于将文件 URL 转换为文件系统路径。

有了 __filename ,就可通过 path 模块的 dirname 方法获取到 __dirname 的值了

import { fileURLToPath } from 'url'
import { dirname } from 'path'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

console.log('__dirname  ', __dirname)
// __dirname   D:\work\code\demo\module-demo6

path.dirname(path) 方法用于获取指定路径的目录名部分。它返回的是去掉文件名后剩下的路径字符串。

总结

在 ES 模块化环境中,由于直接使用 require 会报错,可以通过 Node.js 的 createRequire 方法创建兼容的 require 函数,以此来加载 CommonJS 模块或引入 JSON 文件。

同时,在 ES 模块中,可借助 import.meta.url 来获得在 CommonJS 模块中才可使用的 __dirname__filename 内置变量。