模块:CommonJS 模块#

稳定性:2 - 稳定

CommonJS 模块是为 Node.js 打包 JavaScript 代码的最初方式。Node.js 也支持浏览器和其他 JavaScript 运行时使用的 ECMAScript 模块标准。

在 Node.js 中,每个文件都被视为一个独立的模块。例如,考虑一个名为 foo.js 的文件:

const circle = require('./circle.js');
console.log(`The area of a circle of radius 4 is ${circle.area(4)}`); 

在第一行,foo.js 加载了与 foo.js 位于同一目录下的 circle.js 模块。

以下是 circle.js 的内容:

const { PI } = Math;

exports.area = (r) => PI * r ** 2;

exports.circumference = (r) => 2 * PI * r; 

模块 circle.js 导出了 area()circumference() 函数。通过在特殊的 exports 对象上指定附加属性,函数和对象被添加到模块的根部。

模块内的局部变量将是私有的,因为模块被 Node.js 包装在一个函数中(参见 模块包装器)。在这个例子中,变量 PIcircle.js 是私有的。

可以给 module.exports 属性赋一个新值(例如一个函数或对象)。

在下面的代码中,bar.js 使用了 square 模块,该模块导出一个 Square 类:

const Square = require('./square.js');
const mySquare = new Square(2);
console.log(`The area of mySquare is ${mySquare.area()}`); 

square 模块定义在 square.js 中:

// Assigning to exports will not modify module, must use module.exports
module.exports = class Square {
  constructor(width) {
    this.width = width;
  }

  area() {
    return this.width ** 2;
  }
}; 

CommonJS 模块系统是在 module 核心模块中实现的。

启用#

Node.js 有两个模块系统:CommonJS 模块和 ECMAScript 模块

默认情况下,Node.js 会将以下内容视为 CommonJS 模块:

  • 扩展名为 .cjs 的文件;

  • 扩展名为 .js 的文件,且其最近的父 package.json 文件包含一个值为 "commonjs" 的顶层字段 "type"

  • 扩展名为 .js 或没有扩展名的文件,当最近的父级 package.json 文件不包含顶层字段 "type",或者任何父级文件夹中都没有 package.json 时;除非文件包含的语法只有在作为 ES 模块评估时才会报错。包作者应包含 "type" 字段,即使在所有源文件都是 CommonJS 的包中也是如此。明确包的类型将使构建工具和加载器更容易确定如何解释包中的文件。

  • 文件扩展名不是 .mjs.cjs.json.node.js(当最近的父 package.json 文件包含一个值为 "module" 的顶层字段 "type" 时,这些文件只有在通过 require() 包含时才会被识别为 CommonJS 模块,而不是作为程序的命令行入口点时)。

更多详情请参见确定模块系统

调用 require() 总是使用 CommonJS 模块加载器。调用 import() 总是使用 ECMAScript 模块加载器。

访问主模块#

当一个文件直接从 Node.js 运行时,require.main 会被设置为它的 module。这意味着可以通过测试 require.main === module 来判断一个文件是否被直接运行。

对于文件 foo.js,如果通过 node foo.js 运行,这将是 true,但如果通过 require('./foo') 运行,则为 false

当入口点不是 CommonJS 模块时,require.mainundefined,并且主模块不可访问。

包管理器提示#

Node.js require() 函数的语义设计得足够通用,以支持合理的目录结构。像 dpkgrpmnpm 这样的包管理器程序有望能够无需修改就从 Node.js 模块构建原生包。

下面,我们给出一个可行的建议目录结构:

假设我们希望 /usr/lib/node// 目录存放特定版本包的内容。

包可以相互依赖。为了安装包 foo,可能需要安装特定版本的包 barbar 包本身也可能有依赖,在某些情况下,这些依赖甚至可能冲突或形成循环依赖。

因为 Node.js 会查找它加载的任何模块的 realpath(即,它会解析符号链接),然后node_modules 文件夹中查找它们的依赖项,所以这种情况可以通过以下架构来解决:

  • /usr/lib/node/foo/1.2.3/foo 包版本 1.2.3 的内容。
  • /usr/lib/node/bar/4.3.2/foo 依赖的 bar 包的内容。
  • /usr/lib/node/foo/1.2.3/node_modules/bar:指向 /usr/lib/node/bar/4.3.2/ 的符号链接。
  • /usr/lib/node/bar/4.3.2/node_modules/*:指向 bar 所依赖的包的符号链接。

因此,即使遇到循环或依赖冲突,每个模块也都能获得它能使用的依赖版本。

foo 包中的代码执行 require('bar') 时,它将获取到符号链接到 /usr/lib/node/foo/1.2.3/node_modules/bar 的版本。然后,当 bar 包中的代码调用 require('quux') 时,它将获取到符号链接到 /usr/lib/node/bar/4.3.2/node_modules/quux 的版本。

此外,为了使模块查找过程更加优化,我们可以不直接将包放在 /usr/lib/node 中,而是将它们放在 /usr/lib/node_modules// 中。这样,Node.js 就不会费力在 /usr/node_modules/node_modules 中查找缺失的依赖项。

为了使模块在 Node.js REPL 中可用,将 /usr/lib/node_modules 文件夹添加到 $NODE_PATH 环境变量中可能也很有用。由于使用 node_modules 文件夹的模块查找都是相对的,并且基于调用 require() 的文件的真实路径,因此包本身可以位于任何地方。

使用 require() 加载 ECMAScript 模块#

稳定性:1.2 - 候选发布

.mjs 扩展名是为 ECMAScript 模块保留的。有关哪些文件被解析为 ECMAScript 模块的更多信息,请参阅确定模块系统部分。

require() 仅支持加载满足以下要求的 ECMAScript 模块:

  • 模块是完全同步的(不包含顶层 await);并且
  • 满足以下条件之一:
    1. 文件扩展名为 .mjs
    2. 文件扩展名为 .js,且最近的 package.json 包含 "type": "module"
    3. 文件扩展名为 .js,最近的 package.json 不包含 "type": "commonjs",且模块包含 ES 模块语法。

如果正在加载的 ES 模块满足要求,require() 可以加载它并返回模块命名空间对象。在这种情况下,它类似于动态 import(),但是同步运行并直接返回命名空间对象。

对于以下 ES 模块:

// distance.mjs
export function distance(a, b) { return Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2); } 
// point.mjs
export default class Point {
  constructor(x, y) { this.x = x; this.y = y; }
} 

CommonJS 模块可以使用 require() 加载它们:

const distance = require('./distance.mjs');
console.log(distance);
// [Module: null prototype] {
//   distance: [Function: distance]
// }

const point = require('./point.mjs');
console.log(point);
// [Module: null prototype] {
//   default: [class Point],
//   __esModule: true,
// } 

为了与现有将 ES 模块转换为 CommonJS 的工具互操作,这些工具随后可以通过 require() 加载真正的 ES 模块,如果返回的命名空间有 `default` 导出,它将包含一个 `__esModule: true` 属性,以便工具生成的消费代码可以识别真实 ES 模块中的默认导出。如果命名空间已经定义了 `__esModule`,则不会添加此属性。此属性是实验性的,未来可能会更改。它只应由将 ES 模块转换为 CommonJS 模块的工具使用,遵循现有的生态系统惯例。直接在 CommonJS 中编写的代码应避免依赖它。

当一个 ES 模块同时包含命名导出和默认导出时,require() 返回的结果是模块命名空间对象,它将默认导出放在 .default 属性中,类似于 import() 返回的结果。要自定义 require(esm) 直接返回的内容,ES 模块可以使用字符串名称 "module.exports" 导出所需的值。

// point.mjs
export default class Point {
  constructor(x, y) { this.x = x; this.y = y; }
}

// `distance` is lost to CommonJS consumers of this module, unless it's
// added to `Point` as a static property.
export function distance(a, b) { return Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2); }
export { Point as 'module.exports' } 
const Point = require('./point.mjs');
console.log(Point); // [class Point]

// Named exports are lost when 'module.exports' is used
const { distance } = require('./point.mjs');
console.log(distance); // undefined 

请注意在上面的例子中,当使用 module.exports 导出名称时,命名导出对于 CommonJS 消费者将会丢失。为了让 CommonJS 消费者继续访问命名导出,模块可以确保默认导出是一个对象,并将命名导出作为其属性附加。例如,在上面的例子中,distance 可以作为静态方法附加到默认导出,即 Point 类上。

export function distance(a, b) { return Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2); }

export default class Point {
  constructor(x, y) { this.x = x; this.y = y; }
  static distance = distance;
}

export { Point as 'module.exports' } 
const Point = require('./point.mjs');
console.log(Point); // [class Point]

const { distance } = require('./point.mjs');
console.log(distance); // [Function: distance] 

如果被 require() 的模块包含顶层 await,或者它 import 的模块图中包含顶层 await,将会抛出 ERR_REQUIRE_ASYNC_MODULE。在这种情况下,用户应该使用 import() 来加载异步模块。

如果启用了 --experimental-print-required-tla,Node.js 将不会在评估前抛出 ERR_REQUIRE_ASYNC_MODULE,而是会评估该模块,尝试定位顶层 await,并打印它们的位置以帮助用户修复。

目前,使用 require() 加载 ES 模块的支持是实验性的,可以使用 --no-experimental-require-module 禁用。要打印此功能的使用位置,请使用 --trace-require-module

可以通过检查 process.features.require_module 是否为 true 来检测此功能。

总结#

要获取调用 require() 时将加载的确切文件名,请使用 require.resolve() 函数。

综合以上所有内容,以下是 require() 所做工作的高级伪代码算法:

require(X) from module at path Y
1. If X is a core module,
   a. return the core module
   b. STOP
2. If X begins with '/'
   a. set Y to the file system root
3. If X is equal to '.', or X begins with './', '/' or '../'
   a. LOAD_AS_FILE(Y + X)
   b. LOAD_AS_DIRECTORY(Y + X)
   c. THROW "not found"
4. If X begins with '#'
   a. LOAD_PACKAGE_IMPORTS(X, dirname(Y))
5. LOAD_PACKAGE_SELF(X, dirname(Y))
6. LOAD_NODE_MODULES(X, dirname(Y))
7. THROW "not found"

MAYBE_DETECT_AND_LOAD(X)
1. If X parses as a CommonJS module, load X as a CommonJS module. STOP.
2. Else, if the source code of X can be parsed as ECMAScript module using
  <a href="esm.md#resolver-algorithm-specification">DETECT_MODULE_SYNTAX defined in
  the ESM resolver</a>,
  a. Load X as an ECMAScript module. STOP.
3. THROW the SyntaxError from attempting to parse X as CommonJS in 1. STOP.

LOAD_AS_FILE(X)
1. If X is a file, load X as its file extension format. STOP
2. If X.js is a file,
    a. Find the closest package scope SCOPE to X.
    b. If no scope was found
      1. MAYBE_DETECT_AND_LOAD(X.js)
    c. If the SCOPE/package.json contains "type" field,
      1. If the "type" field is "module", load X.js as an ECMAScript module. STOP.
      2. If the "type" field is "commonjs", load X.js as a CommonJS module. STOP.
    d. MAYBE_DETECT_AND_LOAD(X.js)
3. If X.json is a file, load X.json to a JavaScript Object. STOP
4. If X.node is a file, load X.node as binary addon. STOP

LOAD_INDEX(X)
1. If X/index.js is a file
    a. Find the closest package scope SCOPE to X.
    b. If no scope was found, load X/index.js as a CommonJS module. STOP.
    c. If the SCOPE/package.json contains "type" field,
      1. If the "type" field is "module", load X/index.js as an ECMAScript module. STOP.
      2. Else, load X/index.js as a CommonJS module. STOP.
2. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
3. If X/index.node is a file, load X/index.node as binary addon. STOP

LOAD_AS_DIRECTORY(X)
1. If X/package.json is a file,
   a. Parse X/package.json, and look for "main" field.
   b. If "main" is a falsy value, GOTO 2.
   c. let M = X + (json main field)
   d. LOAD_AS_FILE(M)
   e. LOAD_INDEX(M)
   f. LOAD_INDEX(X) DEPRECATED
   g. THROW "not found"
2. LOAD_INDEX(X)

LOAD_NODE_MODULES(X, START)
1. let DIRS = NODE_MODULES_PATHS(START)
2. for each DIR in DIRS:
   a. LOAD_PACKAGE_EXPORTS(X, DIR)
   b. LOAD_AS_FILE(DIR/X)
   c. LOAD_AS_DIRECTORY(DIR/X)

NODE_MODULES_PATHS(START)
1. let PARTS = path split(START)
2. let I = count of PARTS - 1
3. let DIRS = []
4. while I >= 0,
   a. if PARTS[I] = "node_modules", GOTO d.
   b. DIR = path join(PARTS[0 .. I] + "node_modules")
   c. DIRS = DIR + DIRS
   d. let I = I - 1
5. return DIRS + GLOBAL_FOLDERS

LOAD_PACKAGE_IMPORTS(X, DIR)
1. Find the closest package scope SCOPE to DIR.
2. If no scope was found, return.
3. If the SCOPE/package.json "imports" is null or undefined, return.
4. If `--experimental-require-module` is enabled
  a. let CONDITIONS = ["node", "require", "module-sync"]
  b. Else, let CONDITIONS = ["node", "require"]
5. let MATCH = PACKAGE_IMPORTS_RESOLVE(X, pathToFileURL(SCOPE),
  CONDITIONS) <a href="esm.md#resolver-algorithm-specification">defined in the ESM resolver</a>.
6. RESOLVE_ESM_MATCH(MATCH).

LOAD_PACKAGE_EXPORTS(X, DIR)
1. Try to interpret X as a combination of NAME and SUBPATH where the name
   may have a @scope/ prefix and the subpath begins with a slash (`/`).
2. If X does not match this pattern or DIR/NAME/package.json is not a file,
   return.
3. Parse DIR/NAME/package.json, and look for "exports" field.
4. If "exports" is null or undefined, return.
5. If `--experimental-require-module` is enabled
  a. let CONDITIONS = ["node", "require", "module-sync"]
  b. Else, let CONDITIONS = ["node", "require"]
6. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH,
   `package.json` "exports", CONDITIONS) <a href="esm.md#resolver-algorithm-specification">defined in the ESM resolver</a>.
7. RESOLVE_ESM_MATCH(MATCH)

LOAD_PACKAGE_SELF(X, DIR)
1. Find the closest package scope SCOPE to DIR.
2. If no scope was found, return.
3. If the SCOPE/package.json "exports" is null or undefined, return.
4. If the SCOPE/package.json "name" is not the first segment of X, return.
5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(SCOPE),
   "." + X.slice("name".length), `package.json` "exports", ["node", "require"])
   <a href="esm.md#resolver-algorithm-specification">defined in the ESM resolver</a>.
6. RESOLVE_ESM_MATCH(MATCH)

RESOLVE_ESM_MATCH(MATCH)
1. let RESOLVED_PATH = fileURLToPath(MATCH)
2. If the file at RESOLVED_PATH exists, load RESOLVED_PATH as its extension
   format. STOP
3. THROW "not found" 

缓存#

模块在第一次加载后会被缓存。这意味着(除其他事项外)每次调用 require('foo') 都会返回完全相同的对象,如果它解析到相同的文件。

只要 require.cache 未被修改,多次调用 require('foo') 不会导致模块代码被多次执行。这是一个重要特性。有了它,可以返回“部分完成”的对象,从而允许加载传递性依赖,即使它们会导致循环。

要让一个模块多次执行代码,可以导出一个函数,然后调用该函数。

模块缓存的注意事项#

模块是根据其解析后的文件名进行缓存的。由于模块可能会根据调用模块的位置(从 node_modules 文件夹加载)解析为不同的文件名,因此并不能*保证* require('foo') 总是返回完全相同的对象,如果它解析到不同的文件。

此外,在不区分大小写的文件系统或操作系统上,不同的解析文件名可能指向同一个文件,但缓存仍会将它们视为不同的模块,并多次重新加载该文件。例如,require('./foo')require('./FOO') 会返回两个不同的对象,无论 ./foo./FOO 是否是同一个文件。

内置模块#

Node.js 有几个编译到二进制文件中的模块。这些模块在本文档的其他地方有更详细的描述。

内置模块定义在 Node.js 源码中,位于 lib/ 文件夹内。

内置模块可以使用 node: 前缀来识别,这种情况下它会绕过 require 缓存。例如,require('node:http') 将总是返回内置的 HTTP 模块,即使 require.cache 中存在同名条目。

如果将某些内置模块的标识符传递给 require(),它们总是会被优先加载。例如,require('http') 将总是返回内置的 HTTP 模块,即使存在同名文件。

所有内置模块的列表可以从 module.builtinModules 获取。所有模块都列出时没有 node: 前缀,除了那些强制要求该前缀的模块(如下一节所述)。

强制使用 node: 前缀的内置模块#

当通过 require() 加载时,一些内置模块必须使用 node: 前缀进行请求。这个要求是为了防止新引入的内置模块与已经占用该名称的用户态包发生冲突。目前需要 node: 前缀的内置模块有:

这些模块的列表在 module.builtinModules 中公开,包括前缀。

循环#

当存在循环 require() 调用时,一个模块在返回时可能尚未执行完毕。

考虑这种情况:

a.js:

console.log('a starting');
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done'); 

b.js:

console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done'); 

main.js:

console.log('main starting');
const a = require('./a.js');
const b = require('./b.js');
console.log('in main, a.done = %j, b.done = %j', a.done, b.done); 

main.js 加载 a.js 时,a.js 接着加载 b.js。此时,b.js 试图加载 a.js。为了防止无限循环,一个**未完成的副本** a.js 的 exports 对象会返回给 b.js 模块。然后 b.js 完成加载,其 exports 对象被提供给 a.js 模块。

main.js 加载完两个模块时,它们都已经完成了。因此,这个程序的输出将是:

$ node main.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done = true, b.done = true 

需要仔细规划,以允许循环模块依赖在应用程序中正常工作。

文件模块#

如果找不到确切的文件名,Node.js 将尝试加载带有附加扩展名的所需文件名:.js.json,最后是 .node。当加载具有不同扩展名(例如 .cjs)的文件时,其完整名称必须传递给 require(),包括其文件扩展名(例如 require('./file.cjs'))。

.json 文件被解析为 JSON 文本文件,.node 文件被解释为使用 process.dlopen() 加载的已编译插件模块。使用任何其他扩展名(或根本没有扩展名)的文件被解析为 JavaScript 文本文件。请参考确定模块系统部分,以了解将使用哪种解析目标。

'/' 为前缀的必需模块是文件的绝对路径。例如,require('/home/marco/foo.js') 将加载位于 /home/marco/foo.js 的文件。

'./' 为前缀的必需模块是相对于调用 require() 的文件。也就是说,circle.js 必须与 foo.js 在同一目录下,require('./circle') 才能找到它。

如果没有前导的 '/''./''../' 来指示文件,模块必须是核心模块或从 node_modules 文件夹加载。

如果给定的路径不存在,require() 将抛出一个 MODULE_NOT_FOUND 错误。

文件夹作为模块#

有三种方式可以将一个文件夹作为参数传递给 require()

第一种是在文件夹的根目录下创建一个 package.json 文件,其中指定一个 main 模块。一个示例 package.json 文件可能如下所示:

{ "name" : "some-library",
  "main" : "./lib/some-library.js" } 

如果这位于 ./some-library 文件夹中,那么 require('./some-library') 将尝试加载 ./some-library/lib/some-library.js

如果目录中不存在 package.json 文件,或者 "main" 条目缺失或无法解析,那么 Node.js 将尝试从该目录加载 index.jsindex.node 文件。例如,如果在前面的示例中没有 package.json 文件,那么 require('./some-library') 将尝试加载:

  • ./some-library/index.js
  • ./some-library/index.node

如果这些尝试失败,Node.js 将报告整个模块丢失,并显示默认错误:

Error: Cannot find module 'some-library' 

在上述所有三种情况下,调用 import('./some-library') 将导致 ERR_UNSUPPORTED_DIR_IMPORT 错误。使用包的子路径导出子路径导入可以提供与文件夹作为模块相同的包容性组织优势,并且对 requireimport 都有效。

node_modules 文件夹加载#

如果传递给 require() 的模块标识符不是内置模块,并且不以 '/''../''./' 开头,那么 Node.js 会从当前模块的目录开始,添加 /node_modules,并尝试从该位置加载模块。Node.js 不会将 node_modules 附加到已经以 node_modules 结尾的路径上。

如果在那里找不到,它会移动到父目录,依此类推,直到到达文件系统的根目录。

例如,如果位于 '/home/ry/projects/foo.js' 的文件调用了 require('bar.js'),那么 Node.js 将按以下顺序查找以下位置:

  • /home/ry/projects/node_modules/bar.js
  • /home/ry/node_modules/bar.js
  • /home/node_modules/bar.js
  • /node_modules/bar.js

这允许程序本地化其依赖项,从而避免冲突。

可以通过在模块名后附加路径后缀来 require 模块附带的特定文件或子模块。例如,require('example-module/path/to/file') 将相对于 example-module 所在的位置解析 path/to/file。这个带后缀的路径遵循相同的模块解析语义。

从全局文件夹加载#

如果 NODE_PATH 环境变量被设置为一个以冒号分隔的绝对路径列表,那么 Node.js 在其他地方找不到模块时,会搜索这些路径。

在 Windows 上,NODE_PATH 使用分号 (;) 而不是冒号进行分隔。

NODE_PATH 最初是为了在当前的模块解析算法定义之前支持从不同路径加载模块而创建的。

NODE_PATH 仍然受支持,但现在 Node.js 生态系统已经就定位依赖模块的约定达成一致,因此它的必要性降低了。有时依赖 NODE_PATH 的部署会在人们不知道必须设置 NODE_PATH 时表现出令人惊讶的行为。有时模块的依赖关系会改变,导致在搜索 NODE_PATH 时加载了不同版本(甚至是不同的模块)。

此外,Node.js 会在以下 GLOBAL_FOLDERS 列表中搜索:

  • 1: $HOME/.node_modules
  • 2: $HOME/.node_libraries
  • 3: $PREFIX/lib/node

其中 $HOME 是用户的主目录,$PREFIX 是 Node.js 配置的 node_prefix

这些主要是出于历史原因。

强烈建议将依赖项放置在本地的 node_modules 文件夹中。这样加载速度更快,也更可靠。

模块包装器#

在模块代码执行之前,Node.js 会用一个函数包装器将其包裹起来,如下所示:

(function(exports, require, module, __filename, __dirname) {
// Module code actually lives in here
}); 

通过这样做,Node.js 实现了几件事:

  • 它将顶层变量(用 varconstlet 定义)的作用域限定在模块内,而不是全局对象。
  • 它有助于提供一些看起来是全局的但实际上是模块特有的变量,例如:
    • moduleexports 对象,实现者可以用它们从模块中导出值。
    • 便利变量 __filename__dirname,包含模块的绝对文件名和目录路径。

模块作用域#

__dirname#

当前模块的目录名。这与 __filenamepath.dirname() 相同。

示例:从 /Users/mjr 运行 node example.js

console.log(__dirname);
// Prints: /Users/mjr
console.log(path.dirname(__filename));
// Prints: /Users/mjr 

__filename#

当前模块的文件名。这是当前模块文件的绝对路径,并解析了符号链接。

对于主程序,这不一定与命令行中使用的文件名相同。

有关当前模块的目录名,请参见 __dirname

示例

/Users/mjr 运行 node example.js

console.log(__filename);
// Prints: /Users/mjr/example.js
console.log(__dirname);
// Prints: /Users/mjr 

给定两个模块:ab,其中 ba 的一个依赖,目录结构如下:

  • /Users/mjr/app/a.js
  • /Users/mjr/app/node_modules/b/b.js

b.js 中对 __filename 的引用将返回 /Users/mjr/app/node_modules/b/b.js,而在 a.js 中对 __filename 的引用将返回 /Users/mjr/app/a.js

exports#

module.exports 的引用,输入更简短。有关何时使用 exports 和何时使用 module.exports 的详细信息,请参见关于exports 快捷方式的部分。

module#

对当前模块的引用,请参阅关于module 对象的部分。特别是,module.exports 用于定义一个模块导出什么并通过 require() 提供什么。

require(id)#

  • id <string> 模块名称或路径
  • 返回:<any> 导出的模块内容

用于导入模块、JSON 和本地文件。模块可以从 node_modules 导入。本地模块和 JSON 文件可以使用相对路径(例如 ././foo./bar/baz../foo)导入,该路径将相对于由 __dirname(如果已定义)或当前工作目录命名的目录进行解析。POSIX 风格的相对路径以操作系统无关的方式解析,这意味着上述示例在 Windows 上的工作方式与在 Unix 系统上相同。

// Importing a local module with a path relative to the `__dirname` or current
// working directory. (On Windows, this would resolve to .\path\myLocalModule.)
const myLocalModule = require('./path/myLocalModule');

// Importing a JSON file:
const jsonData = require('./path/filename.json');

// Importing a module from node_modules or Node.js built-in module:
const crypto = require('node:crypto'); 
require.cache#

模块在被 require 时会缓存在此对象中。通过从此对象中删除一个键值,下一次 require 将会重新加载该模块。这不适用于原生插件,因为重新加载会导致错误。

也可以添加或替换条目。此缓存在内置模块之前检查,如果一个与内置模块名称匹配的名称被添加到缓存中,则只有带 `node:` 前缀的 require 调用才会接收到内置模块。请谨慎使用!

const assert = require('node:assert');
const realFs = require('node:fs');

const fakeFs = {};
require.cache.fs = { exports: fakeFs };

assert.strictEqual(require('fs'), fakeFs);
assert.strictEqual(require('node:fs'), realFs); 
require.extensions#

稳定性: 0 - 废弃

指示 `require` 如何处理某些文件扩展名。

将扩展名为 .sjs 的文件作为 .js 处理:

require.extensions['.sjs'] = require.extensions['.js']; 

已弃用。 过去,这个列表曾被用来通过按需编译将非 JavaScript 模块加载到 Node.js 中。然而,在实践中,有更好的方法可以做到这一点,例如通过其他 Node.js 程序加载模块,或者提前将它们编译成 JavaScript。

避免使用 require.extensions。使用它可能会导致细微的错误,并且每注册一个扩展名,解析扩展名的速度就会变慢。

require.main#

代表 Node.js 进程启动时加载的入口脚本的 Module 对象,如果程序的入口点不是 CommonJS 模块,则为 undefined。参见“访问主模块”

entry.js 脚本中:

console.log(require.main); 
node entry.js 
Module {
  id: '.',
  path: '/absolute/path/to',
  exports: {},
  filename: '/absolute/path/to/entry.js',
  loaded: false,
  children: [],
  paths:
   [ '/absolute/path/to/node_modules',
     '/absolute/path/node_modules',
     '/absolute/node_modules',
     '/node_modules' ] } 
require.resolve(request[, options])#
  • request <string> 要解析的模块路径。
  • options <Object>
    • paths <string[]> 用于解析模块位置的路径。如果存在,这些路径将替代默认的解析路径,但全局文件夹(如 $HOME/.node_modules)总是被包含在内。这些路径中的每一个都用作模块解析算法的起点,意味着会从这个位置开始检查 node_modules 层次结构。
  • 返回: <string>

使用内部的 require() 机制来查找模块的位置,但不是加载模块,而是只返回解析后的文件名。

如果找不到模块,会抛出 MODULE_NOT_FOUND 错误。

require.resolve.paths(request)#

返回一个包含在解析 request 过程中搜索的路径的数组,如果 request 字符串引用的是核心模块(例如 httpfs),则返回 null

module 对象#

在每个模块中,自由变量 module 是对代表当前模块的对象的引用。为方便起见,module.exports 也可以通过模块全局变量 exports 访问。module 实际上不是一个全局变量,而是每个模块的局部变量。

module.children#

首次被此模块所 require 的模块对象。

module.exports#

module.exports 对象是由 Module 系统创建的。有时这不被接受;许多人希望他们的模块是某个类的实例。要做到这一点,需要将期望的导出对象赋给 module.exports。将期望的对象赋给 exports 只会重新绑定局部的 exports 变量,这可能不是所期望的。

例如,假设我们正在创建一个名为 a.js 的模块:

const EventEmitter = require('node:events');

module.exports = new EventEmitter();

// Do some work, and after some time emit
// the 'ready' event from the module itself.
setTimeout(() => {
  module.exports.emit('ready');
}, 1000); 

然后在另一个文件中我们可以这样做:

const a = require('./a');
a.on('ready', () => {
  console.log('module "a" is ready');
}); 

module.exports 的赋值必须立即完成。它不能在任何回调中完成。这样做是行不通的:

x.js:

setTimeout(() => {
  module.exports = { a: 'hello' };
}, 0); 

y.js:

const x = require('./x');
console.log(x.a); 
exports 快捷方式#

exports 变量在模块的文件级作用域内可用,并在模块评估之前被赋予 module.exports 的值。

它提供了一个快捷方式,这样 module.exports.f = ... 就可以更简洁地写成 exports.f = ...。但是,请注意,像任何变量一样,如果给 exports 赋了一个新值,它就不再绑定到 module.exports

module.exports.hello = true; // Exported from require of module
exports = { hello: false };  // Not exported, only available in the module 

module.exports 属性被一个新对象完全替换时,通常也会重新分配 exports

module.exports = exports = function Constructor() {
  // ... etc.
}; 

为了说明这种行为,想象一下 require() 的这个假设实现,它与 require() 的实际实现非常相似:

function require(/* ... */) {
  const module = { exports: {} };
  ((module, exports) => {
    // Module code here. In this example, define a function.
    function someFunc() {}
    exports = someFunc;
    // At this point, exports is no longer a shortcut to module.exports, and
    // this module will still export an empty default object.
    module.exports = someFunc;
    // At this point, the module will now export someFunc, instead of the
    // default object.
  })(module, module.exports);
  return module.exports;
} 

module.filename#

模块的完全解析后的文件名。

module.id#

模块的标识符。通常这是完全解析后的文件名。

module.isPreloading#

  • 类型:<boolean> 如果模块在 Node.js 预加载阶段运行,则为 true

module.loaded#

模块是否已加载完成,或正在加载过程中。

module.parent#

稳定性:0 - 已弃用:请改用 require.mainmodule.children

第一个 require 此模块的模块,如果当前模块是当前进程的入口点,则为 null;如果模块是由非 CommonJS 模块(例如:REPL 或 import)加载的,则为 undefined

module.path#

模块的目录名。这通常与 module.idpath.dirname() 相同。

module.paths#

模块的搜索路径。

module.require(id)#

module.require() 方法提供了一种加载模块的方式,就好像是从原始模块调用 require() 一样。

为了做到这一点,有必要获取对 module 对象的引用。由于 require() 返回的是 module.exports,而 module 通常*只*在特定模块的代码中可用,因此必须明确导出才能使用。

Module 对象#

此部分已移至 模块:module 核心模块

Source map v3 支持#

此部分已移至模块:`module` 核心模块