NodeJS 中的 esModule 与 commonJS

NodeJS 从 v14 版本开始正式支持解析 esModule,通过扩展 .mjs.cjs 后缀,可以将 js 文件解析为 esModule 和 commonJS。

此外,也可以在文件目录下单独设置一个 package.json,声明 { "type": "module },默认在当前目录下使用 esModule 规范,进行模块解析。

那么,Node 当中的 esModule 和 commonJS 有哪些不同呢?

模块加载

在 commonJS 中require() 的执行是同步的。它在调用后并不会返回一个 Promise 或者回调函数,而是直接从磁盘或者网络中读取资源(可能涉及到磁盘或网络的 I/O),并立即执行脚本中的内容,返回最后将 module.exports 的值。

在 esModule 中,模块的加载是异步的。首先,模块解析器(module loader)会对脚本进行静态解析,无需执行脚本中的内容,就可以分析出所有的 importexport 调用。在解析过程中,解析器可以快速地检查脚本引入或导出模块时的语法错误,并及时抛出异常。

esModule 的解析器会异步的下载与解析代码中引入的脚本,递归的分析模块间的引用关系,构建出完成的「模块依赖图谱」(module graph)。当解析完成后,对应的脚本以及它所依赖的脚本,就可以等待执行了。

所有的脚本都是并行下载的,并且按照引入顺序执行。

路径解析

在 esModule 中使用 import 时,必须完成声明文件路径,包括文件扩展名,如 './src/index.js'

在 node v14 版本中,使用 esModule 必须遵守 file: 协议,即 URL-based paths

环境变量

commonJS 中的环境变量/对象,包括 require, exports, module.exports, __filename, __dirname,在 esModule 中都是无法使用的。

如果需要在 esModule 使用 require,可以通过 module.createRequire()

如果需要使用 __filename, __dirname,可以通过 import.meta.url 进行读取。

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

console.log(import.meta.url);

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

require.cache

esModule 中无法使用 require.cache,而 commonJS 会缓存引入模块的相关信息。每次 require 模块后,commonJS 会缓存对应的模块,从 cache 中删除某个模块,那么下一次 require 会重新加载这个模块。

commonJS 中 require.cache 的数据结构如下:

{
  "/esModule-cmd/cmd/env/index.js": {
    "id": ".",
    "path": "/esModule-cmd/cmd/env",
    "exports": {},
    "parent": null,
    "filename": "/esModule-cmd/cmd/env/index.js",
    "loaded": false,
    "children": [[Module]],
    "paths": [
      "/esModule-cmd/cmd/env/node_modules",
      "/esModule-cmd/cmd/node_modules",
      "/esModule-cmd/node_modules",
      "/node_modules"
    ]
  },
  "/esModule-cmd/cmd/env/a.js": {
    "id": "/esModule-cmd/cmd/env/a.js",
    "path": "/esModule-cmd/cmd/env",
    "exports": {},
    "parent": {
      "id": ".",
      "path": "/esModule-cmd/cmd/env",
      "exports": {},
      "parent": null,
      "filename": "/esModule-cmd/cmd/env/index.js",
      "loaded": false,
      "children": [Array],
      "paths": [Array]
    },
    "filename": "/esModule-cmd/cmd/env/a.js",
    "loaded": false,
    "children": [[Module]],
    "paths": [
      "/esModule-cmd/cmd/env/node_modules",
      "/esModule-cmd/cmd/node_modules",
      "/esModule-cmd/node_modules",
      "/node_modules"
    ]
  }
}

参考

Last updated