Node.js v18.20.8 文档


模块:CommonJS 模块#>

【Modules: CommonJS modules】

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

【CommonJS modules are the original way to package JavaScript code for Node.js. Node.js also supports the ECMAScript modules standard used by browsers and other JavaScript runtimes.】

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

【In Node.js, each file is treated as a separate module. For example, consider a file named 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

【On the first line, foo.js loads the module circle.js that is in the same directory as foo.js.】

以下是 circle.js 的内容:

【Here are the contents of circle.js:】

const { PI } = Math;

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

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

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

【The module circle.js has exported the functions area() and circumference(). Functions and objects are added to the root of a module by specifying additional properties on the special exports object.】

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

【Variables local to the module will be private, because the module is wrapped in a function by Node.js (see module wrapper). In this example, the variable PI is private to circle.js.】

module.exports 属性可以被分配一个新的值(例如函数或对象)。

【The module.exports property can be assigned a new value (such as a function or object).】

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

【Below, bar.js makes use of the square module, which exports a Square class:】

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

square 模块定义在 square.js 中:

【The square module is defined in 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 核心模块 中实现。

【The CommonJS module system is implemented in the module core module.】

启用#>

【Enabling】

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

【Node.js has two module systems: CommonJS modules and ECMAScript modules.】

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

【By default, Node.js will treat the following as CommonJS modules:】

  • 具有 .cjs 扩展名的文件;

  • 当最近的父文件为“package.json”时,后缀为“.js”的文件 包含一个顶层字段"type",值为“commonjs”。

  • 当最近的父级 package.json 文件中不包含顶层字段 "type" 时,扩展名为 .js 的文件。即使在所有源文件都是 CommonJS 的包中,包作者也应包含 "type" 字段。明确指定包的 type 将使构建工具和加载器更容易确定包中的文件应如何被解释。

  • 副名不是 '.mjs'、'.cjs'、'.json'、'.node' 或 '.js' 的文件 (当最近的父文件“package.json”文件包含顶层字段时 "type" 的值为“module”,这些文件会被识别为 只有当模块通过“require()”被包含时才使用CommonJS模块,不适用于 用作程序的命令行入口)。

有关详细信息,请参见确定模块系统

【See Determining module system for more details.】

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

【Calling require() always use the CommonJS module loader. Calling import() always use the ECMAScript module loader.】

访问主模块#>

【Accessing the main module】

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

【When a file is run directly from Node.js, require.main is set to its module. That means that it is possible to determine whether a file has been run directly by testing require.main === module.】

对于文件 foo.js,如果通过 node foo.js 运行,则此值为 true;如果通过 require('./foo') 运行,则为 false

【For a file foo.js, this will be true if run via node foo.js, but false if run by require('./foo').】

当入口点不是 CommonJS 模块时,require.mainundefined,主模块无法访问。

【When the entry point is not a CommonJS module, require.main is undefined, and the main module is out of reach.】

包管理器提示#>

【Package manager tips】

Node.js require() 函数的语义设计得足够通用,以支持合理的目录结构。希望像 dpkgrpmnpm 这样的包管理程序能够在不修改的情况下从 Node.js 模块构建本地包。

【The semantics of the Node.js require() function were designed to be general enough to support reasonable directory structures. Package manager programs such as dpkg, rpm, and npm will hopefully find it possible to build native packages from Node.js modules without modification.】

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

【Below we give a suggested directory structure that could work:】

假设我们希望位于 /usr/lib/node/<some-package>/<some-version> 的文件夹保存某个特定版本的包的内容。

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

【Packages can depend on one another. In order to install package foo, it may be necessary to install a specific version of package bar. The bar package may itself have dependencies, and in some cases, these may even collide or form cyclic dependencies.】

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

【Because Node.js looks up the realpath of any modules it loads (that is, it resolves symlinks) and then looks for their dependencies in node_modules folders, this situation can be resolved with the following architecture:】

  • /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 所依赖的包的符号链接。

因此,即使遇到循环,或者存在依赖冲突,每个模块仍然能够获得一个可以使用的依赖版本。

【Thus, even if a cycle is encountered, or if there are dependency conflicts, every module will be able to get a version of its dependency that it can use.】

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 的版本。

【When the code in the foo package does require('bar'), it will get the version that is symlinked into /usr/lib/node/foo/1.2.3/node_modules/bar. Then, when the code in the bar package calls require('quux'), it'll get the version that is symlinked into /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() 的文件的真实路径,因此这些包本身可以位于任何地方。

【In order to make modules available to the Node.js REPL, it might be useful to also add the /usr/lib/node_modules folder to the $NODE_PATH environment variable. Since the module lookups using node_modules folders are all relative, and based on the real path of the files making the calls to require(), the packages themselves can be anywhere.】

.mjs 扩展名#>

【The .mjs extension】

由于 require() 的同步特性,无法用它来加载 ECMAScript 模块文件。尝试这样做会抛出 ERR_REQUIRE_ESM 错误。请改用 import()

【Due to the synchronous nature of require(), it is not possible to use it to load ECMAScript module files. Attempting to do so will throw a ERR_REQUIRE_ESM error. Use import() instead.】

.mjs 扩展名是为 ECMAScript 模块 保留的,不能通过 require() 加载。有关哪些文件被解析为 ECMAScript 模块的更多信息,请参见 确定模块系统 章节。

【The .mjs extension is reserved for ECMAScript Modules which cannot be loaded via require(). See Determining module system section for more info regarding which files are parsed as ECMAScript modules.】

全部一起#>

【All together】

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

【To get the exact filename that will be loaded when require() is called, use the require.resolve() function.】

综合以上所有内容,这里是 require() 的高层次算法伪代码:

【Putting together all of the above, here is the high-level algorithm in pseudocode of what require() does:】

从路径为 Y 的模块中 require(X)
1. 如果 X 是核心模块,
   a. 返回核心模块
   b. 停止
2. 如果 X 以 '/' 开头
   a. 将 Y 设置为文件系统根目录
3. 如果 X 以 './'、'/' 或 '../' 开头
   a. 以文件方式加载(Y + X)
   b. 以目录方式加载(Y + X)
   c. 抛出“未找到”错误
4. 如果 X 以 '#' 开头
   a. 加载包导入(X, Y 的目录名)
5. 加载包自身(X, Y 的目录名)
6. 加载 node_modules(X, Y 的目录名)
7. 抛出“未找到”错误

LOAD_AS_FILE(X)
1. 如果 X 是一个文件,则按其文件扩展名格式加载 X。停止
2. 如果 X.js 是一个文件,则将 X.js 作为 JavaScript 文本加载。停止
3. 如果 X.json 是一个文件,则将 X.json 解析为 JavaScript 对象。停止
4. 如果 X.node 是一个文件,则将 X.node 作为二进制插件加载。停止

【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, load X.js as JavaScript text. STOP
3. If X.json is a file, parse X.json to a JavaScript Object. STOP
4. If X.node is a file, load X.node as binary addon. STOP】

加载索引(X)
1. 如果 X/index.js 是一个文件,加载 X/index.js 作为 JavaScript 文本。停止
2. 如果 X/index.json 是一个文件,将 X/index.json 解析为 JavaScript 对象。停止
3. 如果 X/index.node 是一个文件,加载 X/index.node 作为二进制插件。停止

【LOAD_INDEX(X)
1. If X/index.js is a file, load X/index.js as JavaScript text. 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. 如果 X/package.json 是一个文件,
   a. 解析 X/package.json,并查找 "main" 字段。
   b. 如果 "main" 是假值,跳转到 2。
   c. 令 M = X +(json 中的 main 字段)
   d. 以文件方式加载 M(LOAD_AS_FILE(M))
   e. 加载 index(M)(LOAD_INDEX(M))
   f. 加载 index(X)(LOAD_INDEX(X) 已弃用)
   g. 抛出 "未找到"(THROW "not found")
2. 加载 index(X)(LOAD_INDEX(X))

【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)】

加载节点模块(X, START)
1. 让 DIRS = 节点模块路径(START)
2. 对于 DIRS 中的每个 DIR:
   a. 加载包导出(X, DIR)
   b. 作为文件加载(DIR/X)
   c. 作为目录加载(DIR/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. 设 PARTS = path split(START)
2. 设 I = PARTS 的数量 - 1
3. 设 DIRS = []
4. 当 I >= 0 时,
   a. 如果 PARTS[I] = "node_modules",则跳过
   b. DIR = path join(PARTS[0 .. I] + "node_modules")
   c. DIRS = DIR + DIRS
   d. 设 I = I - 1
5. 返回 DIRS + GLOBAL_FOLDERS

【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" CONTINUE
   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. 找到距离 DIR 最近的包作用域 SCOPE。
2. 如果未找到作用域,则返回。
3. 如果 SCOPE/package.json 中的“imports”为 null 或 undefined,则返回。
4. 令 MATCH = PACKAGE_IMPORTS_RESOLVE(X, pathToFileURL(SCOPE), ["node", "require"]) 在 ESM 解析器中定义。
5. RESOLVE_ESM_MATCH(MATCH).

LOAD_PACKAGE_EXPORTS(X, DIR)
1. 尝试将 X 解释为 NAME 和 SUBPATH 的组合,其中 NAME 可能带有 @scope/ 前缀,SUBPATH 以斜杠 (`/`) 开头。
2. 如果 X 不符合此模式或者 DIR/NAME/package.json 不是文件,则返回。
3. 解析 DIR/NAME/package.json,并查找 "exports" 字段。
4. 如果 "exports" 为 null 或 undefined,则返回。
5. 让 MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH, `package.json` "exports", ["node", "require"]) 在 ESM 解析器中定义。
6. RESOLVE_ESM_MATCH(MATCH)

LOAD_PACKAGE_SELF(X, DIR)
1. 找到离 DIR 最近的包作用域 SCOPE。
2. 如果没有找到作用域,则返回。
3. 如果 SCOPE/package.json 中的 "exports" 为 null 或 undefined,则返回。
4. 如果 SCOPE/package.json 中的 "name" 不是 X 的第一个部分,则返回。
5. 令 MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(SCOPE),
   "." + X.slice("name".length), `package.json` 的 "exports", ["node", "require"]) 
   在 ESM 解析器中定义。
6. RESOLVE_ESM_MATCH(MATCH)

RESOLVE_ESM_MATCH(MATCH)
1. 让 RESOLVED_PATH = fileURLToPath(MATCH)
2. 如果 RESOLVED_PATH 对应的文件存在,则按其扩展名格式加载 RESOLVED_PATH。停止
3. 抛出 "未找到"

缓存#>

【Caching】

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

【Modules are cached after the first time they are loaded. This means (among other things) that every call to require('foo') will get exactly the same object returned, if it would resolve to the same file.】

只要 require.cache 没有被修改,多次调用 require('foo') 不会导致模块代码被多次执行。这是一个重要特性。通过它,可以返回“部分完成”的对象,从而即使在可能引起循环依赖的情况下,也能加载传递依赖。

【Provided require.cache is not modified, multiple calls to require('foo') will not cause the module code to be executed multiple times. This is an important feature. With it, "partially done" objects can be returned, thus allowing transitive dependencies to be loaded even when they would cause cycles.】

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

【To have a module execute code multiple times, export a function, and call that function.】

模块缓存注意事项#>

【Module caching caveats】

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

【Modules are cached based on their resolved filename. Since modules may resolve to a different filename based on the location of the calling module (loading from node_modules folders), it is not a guarantee that require('foo') will always return the exact same object, if it would resolve to different files.】

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

【Additionally, on case-insensitive file systems or operating systems, different resolved filenames can point to the same file, but the cache will still treat them as different modules and will reload the file multiple times. For example, require('./foo') and require('./FOO') return two different objects, irrespective of whether or not ./foo and ./FOO are the same file.】

核心模块#>

【Core modules】

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

【Node.js has several modules compiled into the binary. These modules are described in greater detail elsewhere in this documentation.】

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

【The core modules are defined within the Node.js source and are located in the lib/ folder.】

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

【Core modules can be identified using the node: prefix, in which case it bypasses the require cache. For instance, require('node:http') will always return the built in HTTP module, even if there is require.cache entry by that name.】

如果将核心模块的标识符传递给 require(),某些核心模块总是会被优先加载。例如,require('http') 总是会返回内置的 HTTP 模块,即使存在同名文件。可以在不使用 node: 前缀的情况下加载的核心模块列表显示为 module.builtinModules

【Some core modules are always preferentially loaded if their identifier is passed to require(). For instance, require('http') will always return the built-in HTTP module, even if there is a file by that name. The list of core modules that can be loaded without using the node: prefix is exposed as module.builtinModules.】

循环#>

【Cycles】

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

【When there are circular require() calls, a module might not have finished executing when it is returned.】

考虑这种情况:

【Consider this situation:】

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。为了防止无限循环,会向 b.js 模块返回 a.js 导出对象的未完成副本。随后 b.js 完成加载,并将其 exports 对象提供给 a.js 模块。

【When main.js loads a.js, then a.js in turn loads b.js. At that point, b.js tries to load a.js. In order to prevent an infinite loop, an unfinished copy of the a.js exports object is returned to the b.js module. b.js then finishes loading, and its exports object is provided to the a.js module.】

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

【By the time main.js has loaded both modules, they're both finished. The output of this program would thus be:】

$ 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 

需要仔细规划,以确保循环模块依赖在应用中能够正确工作。

【Careful planning is required to allow cyclic module dependencies to work correctly within an application.】

文件模块#>

【File modules】

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

【If the exact filename is not found, then Node.js will attempt to load the required filename with the added extensions: .js, .json, and finally .node. When loading a file that has a different extension (e.g. .cjs), its full name must be passed to require(), including its file extension (e.g. require('./file.cjs')).】

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

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

【A required module prefixed with '/' is an absolute path to the file. For example, require('/home/marco/foo.js') will load the file at /home/marco/foo.js.】

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

【A required module prefixed with './' is relative to the file calling require(). That is, circle.js must be in the same directory as foo.js for require('./circle') to find it.】

如果没有以 '/''./''../' 开头来指示文件,该模块必须是核心模块,或者是从 node_modules 文件夹中加载的。

【Without a leading '/', './', or '../' to indicate a file, the module must either be a core module or is loaded from a node_modules folder.】

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

【If the given path does not exist, require() will throw a MODULE_NOT_FOUND error.】

文件夹作为模块#>

【Folders as modules】

稳定性: 3 - 传统做法:请使用 子路径导出子路径导入

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

【There are three ways in which a folder may be passed to require() as an argument.】

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

【The first is to create a package.json file in the root of the folder, which specifies a main module. An example package.json file might look like this:】

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

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

【If this was in a folder at ./some-library, then require('./some-library') would attempt to load ./some-library/lib/some-library.js.】

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

【If there is no package.json file present in the directory, or if the "main" entry is missing or cannot be resolved, then Node.js will attempt to load an index.js or index.node file out of that directory. For example, if there was no package.json file in the previous example, then require('./some-library') would attempt to load:】

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

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

【If these attempts fail, then Node.js will report the entire module as missing with the default error:】

Error: Cannot find module 'some-library' 

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

【In all three above cases, an import('./some-library') call would result in a ERR_UNSUPPORTED_DIR_IMPORT error. Using package subpath exports or subpath imports can provide the same containment organization benefits as folders as modules, and work for both require and import.】

node_modules 文件夹加载#>

【Loading from node_modules folders】

如果传递给 require() 的模块标识符不是一个核心模块,并且不以 '/''../''./' 开头,则 Node.js 会从当前模块的目录开始,添加 /node_modules,并尝试从该位置加载模块。Node.js 不会向已以 node_modules 结尾的路径追加 node_modules

【If the module identifier passed to require() is not a core module, and does not begin with '/', '../', or './', then Node.js starts at the directory of the current module, and adds /node_modules, and attempts to load the module from that location. Node.js will not append node_modules to a path already ending in node_modules.】

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

【If it is not found there, then it moves to the parent directory, and so on, until the root of the file system is reached.】

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

【For example, if the file at '/home/ry/projects/foo.js' called require('bar.js'), then Node.js would look in the following locations, in this order:】

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

这允许程序将它们的依赖本地化,从而避免冲突。

【This allows programs to localize their dependencies, so that they do not clash.】

可以通过在模块名称后包含路径后缀来要求分发在模块中的特定文件或子模块。例如,require('example-module/path/to/file') 会根据 example-module 所在的位置解析 path/to/file。后缀路径遵循相同的模块解析语义。

【It is possible to require specific files or sub modules distributed with a module by including a path suffix after the module name. For instance require('example-module/path/to/file') would resolve path/to/file relative to where example-module is located. The suffixed path follows the same module resolution semantics.】

从全局文件夹加载#>

【Loading from the global folders】

如果 NODE_PATH 环境变量被设置为由冒号分隔的绝对路径列表,那么如果模块在其他位置没有找到,Node.js 将在这些路径中搜索模块。

【If the NODE_PATH environment variable is set to a colon-delimited list of absolute paths, then Node.js will search those paths for modules if they are not found elsewhere.】

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

【On Windows, NODE_PATH is delimited by semicolons (;) instead of colons.】

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

NODE_PATH 仍然受支持,但现在 Node.js 生态系统已经形成了定位依赖模块的约定,因此它的必要性较小。有时依赖于 NODE_PATH 的部署会表现出令人惊讶的行为,尤其是在用户不知道必须设置 NODE_PATH 时。有时模块的依赖发生变化,导致在搜索 NODE_PATH 时加载了不同的版本(甚至是不同的模块)。

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

【Additionally, Node.js will search in the following list of GLOBAL_FOLDERS:】

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

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

【Where $HOME is the user's home directory, and $PREFIX is the Node.js configured node_prefix.】

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

【These are mostly for historic reasons.】

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

【It is strongly encouraged to place dependencies in the local node_modules folder. These will be loaded faster, and more reliably.】

模块封装器#>

【The module wrapper】

在模块的代码执行之前,Node.js 会用一个函数封装器来封装它,形式如下:

【Before a module's code is executed, Node.js will wrap it with a function wrapper that looks like the following:】

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

通过这样做,Node.js 实现了以下几点:

【By doing this, Node.js achieves a few things:】

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

模块作用域#>

【The module scope】

__dirname#>

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

【The directory name of the current module. This is the same as the path.dirname() of the __filename.】

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

【Example: running node example.js from /Users/mjr

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

__filename#>

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

【The file name of the current module. This is the current module file's absolute path with symlinks resolved.】

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

【For a main program this is not necessarily the same as the file name used in the command line.】

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

【See __dirname for the directory name of the current module.】

示例:

【Examples:】

/Users/mjr 运行 node example.js

【Running node example.js from /Users/mjr

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

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

【Given two modules: a and b, where b is a dependency of a and there is a directory structure of:】

  • /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

【References to __filename within b.js will return /Users/mjr/app/node_modules/b/b.js while references to __filename within a.js will return /Users/mjr/app/a.js.】

exports#>

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

【A reference to the module.exports that is shorter to type. See the section about the exports shortcut for details on when to use exports and when to use module.exports.】

module#>

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

【A reference to the current module, see the section about the module object. In particular, module.exports is used for defining what a module exports and makes available through require().】

require(id)#>

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

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

【Used to import modules, JSON, and local files. Modules can be imported from node_modules. Local modules and JSON files can be imported using a relative path (e.g. ./, ./foo, ./bar/baz, ../foo) that will be resolved against the directory named by __dirname (if defined) or the current working directory. The relative paths of POSIX style are resolved in an OS independent fashion, meaning that the examples above will work on Windows in the same way they would on Unix systems.】

// 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 时将会重新加载该模块。这不适用于 本地插件,重新加载 本地插件 会导致错误。

【Modules are cached in this object when they are required. By deleting a key value from this object, the next require will reload the module. This does not apply to native addons, for which reloading will result in an error.】

添加或替换条目也是可能的。在检查内置模块之前会先检查此缓存,如果将与内置模块同名的条目添加到缓存中,只有带有 node: 前缀的 require 调用才能接收到内置模块。请谨慎使用!

【Adding or replacing entries is also possible. This cache is checked before built-in modules and if a name matching a built-in module is added to the cache, only node:-prefixed require calls are going to receive the built-in module. Use with care!】

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 如何处理某些文件扩展名。

【Instruct require on how to handle certain file extensions.】

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

【Process files with the extension .sjs as .js:】

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

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

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

【Avoid using require.extensions. Use could cause subtle bugs and resolving the extensions gets slower with each registered extension.】

require.main#>

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

【The Module object representing the entry script loaded when the Node.js process launched, or undefined if the entry point of the program is not a CommonJS module. See "Accessing the main module".】

entry.js 脚本中:

【In entry.js script:】

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() 机制查找模块的位置,但不是加载模块,而是直接返回解析后的文件名。

【Use the internal require() machinery to look up the location of a module, but rather than loading the module, just return the resolved filename.】

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

【If the module can not be found, a MODULE_NOT_FOUND error is thrown.】

require.resolve.paths(request)#>

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

【Returns an array containing the paths searched during resolution of request or null if the request string references a core module, for example http or fs.】

module 对象#>

【The module object】

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

【In each module, the module free variable is a reference to the object representing the current module. For convenience, module.exports is also accessible via the exports module-global. module is not actually a global but rather local to each module.】

module.children#>

  • 模块[]

这个模块首次需要的对象。

【The module objects required for the first time by this one.】

module.exports#>

module.exports 对象是由 Module 系统创建的。有时候这是不可接受的;很多人希望他们的模块是某个类的实例。要实现这一点,可以将所需的导出对象赋值给 module.exports。将所需对象赋值给 exports 只会重新绑定本地 exports 变量,这可能不是所希望的效果。

【The module.exports object is created by the Module system. Sometimes this is not acceptable; many want their module to be an instance of some class. To do this, assign the desired export object to module.exports. Assigning the desired object to exports will simply rebind the local exports variable, which is probably not what is desired.】

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

【For example, suppose we were making a module called 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); 

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

【Then in another file we could do:】

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

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

【Assignment to module.exports must be done immediately. It cannot be done in any callbacks. This does not work:】

x.js:

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

y.js

const x = require('./x');
console.log(x.a); 

exports 快捷方式#>

exports shortcut】

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

【The exports variable is available within a module's file-level scope, and is assigned the value of module.exports before the module is evaluated.】

它允许使用快捷方式,因此 module.exports.f = ... 可以更简洁地写作 exports.f = ...。但是,需要注意的是,和任何变量一样,如果给 exports 赋予一个新值,它将不再与 module.exports 绑定:

【It allows a shortcut, so that module.exports.f = ... can be written more succinctly as exports.f = .... However, be aware that like any variable, if a new value is assigned to exports, it is no longer bound to module.exports:】

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

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

【When the module.exports property is being completely replaced by a new object, it is common to also reassign exports:】

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

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

【To illustrate the behavior, imagine this hypothetical implementation of require(), which is quite similar to what is actually done by 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#>

模块的完全解析文件名。

【The fully resolved filename of the module.】

module.id#>

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

【The identifier for the module. Typically this is the fully resolved filename.】

module.isPreloading#>

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

module.loaded#>

无论模块是否已完成加载,或正在加载中。

【Whether or not the module is done loading, or is in the process of loading.】

module.parent#>

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

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

【The module that first required this one, or null if the current module is the entry point of the current process, or undefined if the module was loaded by something that is not a CommonJS module (E.G.: REPL or import).】

module.path#>

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

【The directory name of the module. This is usually the same as the path.dirname() of the module.id.】

module.paths#>

  • 字符串[]

模块的搜索路径。

【The search paths for the module.】

module.require(id)#>

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

【The module.require() method provides a way to load a module as if require() was called from the original module.】

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

【In order to do this, it is necessary to get a reference to the module object. Since require() returns the module.exports, and the module is typically only available within a specific module's code, it must be explicitly exported in order to be used.】

Module 对象#>

【The Module object】

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

【This section was moved to Modules: module core module.】

源映射 v3 支持#>

【Source map v3 support】

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

【This section was moved to Modules: module core module.】

Node.js 中文网 - 粤ICP备13048890号