模块: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 扩展名的文件;

  • 当最近的父 package.json 文件包含一个值为 "commonjs" 的顶级字段 "type" 时,具有 .js 扩展名的文件。

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

  • 扩展名不是 .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/<some-package>/<some-version> 包含特定版本的包的内容。

包可以相互依赖。 为了安装包 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_modules/<name>/<version> 中,而不是直接放在 /usr/lib/node 中。 然后 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 模块,返回的命名空间将包含一个 __esModule: true 属性(如果它有一个 default 导出),以便工具生成的消费代码可以识别真实 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()'d 的模块包含顶层 await,或者它 import 的模块图包含顶层 await,则会抛出 ERR_REQUIRE_ASYNC_MODULE。 在这种情况下,用户应使用 import() 加载异步模块。

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

目前对使用 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.cache 条目,require('node:http') 始终会返回内置的 HTTP 模块。

如果将某些内置模块的标识符传递给 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 导出对象的**未完成副本**返回给 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() 加载的已编译的附加模块。 请参阅确定模块系统部分,以了解将使用哪种解析目标。

'/' 为前缀的必需模块是文件的绝对路径。 例如,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('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#

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

示例:从 /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 的详细信息,请参阅有关 导出快捷方式 的部分。

module#

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

require(id)#

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

用于导入模块、JSON 和本地文件。可以从 node_modules 导入模块。 可以使用相对于由 __dirname(如果已定义)或当前工作目录命名的目录解析的相对路径(例如,././foo./bar/baz../foo)导入本地模块和 JSON 文件。 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 将重新加载模块。 这不适用于 原生插件,重新加载原生插件将导致错误。

也可以添加或替换条目。 在检查内置模块之前会检查此缓存,并且如果将与内置模块匹配的名称添加到缓存中,则只有带有 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[]> 用于解析模块位置的路径。 如果存在,则使用这些路径代替默认解析路径,但 GLOBAL_FOLDERS(例如 $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#

此模块第一次需要的模块对象。

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

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

module.path#

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

module.paths#

模块的搜索路径。

module.require(id)#

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

为了做到这一点,有必要获得对 module 对象的引用。 由于 require() 返回 module.exports,并且 module 通常 *仅* 在特定模块的代码中可用,因此必须显式导出它才能使用。

Module 对象#

本节已移动到 模块: module 核心模块

Source map v3 支持#

本节已移动到 模块: module 核心模块