模块:CommonJS 模块
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 将模块包装在一个函数中(参见 模块包装器)。在这个例子中,变量 PI
对 circle.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
中定义:
// 赋值给 exports 不会修改模块,必须使用 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
文件包含一个顶级字段"type"
且其值为"commonjs"
时,扩展名为.js
的文件; - 扩展名为
.js
或没有扩展名的文件,当最近的父package.json
文件不包含顶级字段"type"
或任何父文件夹中不存在package.json
文件时;除非文件包含除非将其评估为 ES 模块才会出错的语法。包作者应包含"type"
字段,即使在所有源都是 CommonJS 的包中也是如此。明确说明包的type
将使构建工具和加载器更容易确定如何解释包中的文件。 - 扩展名不是
.mjs
、.cjs
、.json
、.node
或.js
的文件(当最近的父package.json
文件包含一个顶级字段"type"
且其值为"module"
时,只有在通过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.main
为 undefined
,并且主模块不可访问。
包管理器提示
Node.js require()
函数的语义设计得足够通用,可以支持合理的目录结构。像 dpkg
、rpm
和 npm
这样的包管理器程序有望能够在无需修改的情况下从 Node.js 模块构建原生包。
下面,我们给出一个建议的目录结构:
假设我们希望 /usr/lib/node/\<some-package\>/\<some-version\>
文件夹包含特定版本包的内容。
包可以相互依赖。为了安装包 foo
,可能需要安装特定版本的包 bar
。bar
包本身可能也有依赖项,在某些情况下,这些依赖项甚至可能冲突或形成循环依赖。
因为 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/\<name\>/\<version\>
中。然后 Node.js 将不会费心在 /usr/node_modules
或 /node_modules
中查找缺失的依赖项。
为了使模块可用于 Node.js REPL,将 /usr/lib/node_modules
文件夹添加到 $NODE_PATH
环境变量中可能很有用。由于使用 node_modules
文件夹的模块查找都是相对的,并且基于进行 require()
调用的文件的真实路径,因此包本身可以位于任何位置。
使用 require()
加载 ECMAScript 模块
[历史]
版本 | 变更 |
---|---|
v23.5.0 | 此功能默认不再发出实验性警告,尽管仍然可以通过 --trace-require-module 发出警告。 |
v23.0.0 | 此功能不再受 --experimental-require-module CLI 标志限制。 |
v23.0.0 | 支持在 require(esm) 中进行 'module.exports' 互操作导出。 |
v22.0.0, v20.17.0 | 添加于:v22.0.0, v20.17.0 |
.mjs
扩展名保留用于 ECMAScript 模块。有关哪些文件被解析为 ECMAScript 模块的更多信息,请参阅 确定模块系统 部分。
require()
只支持加载满足以下要求的 ECMAScript 模块:
- 模块完全同步(不包含顶级
await
);并且 - 满足以下条件之一:
如果要加载的 ES 模块满足这些要求,require()
可以加载它并返回模块命名空间对象。在这种情况下,它类似于动态 import()
,但它是同步运行的,并直接返回命名空间对象。
使用以下 ES 模块:
// distance.mjs
export function distance(a, b) {
return (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
}
}
// 除非将其作为静态属性添加到 `Point`,否则 `distance` 将丢失给此模块的 CommonJS 使用者。
export function distance(a, b) {
return (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]
// 使用 'module.exports' 时,命名导出将丢失
const { distance } = require('./point.mjs')
console.log(distance) // undefined
请注意,在上面的示例中,当使用 module.exports
导出名称时,命名导出将丢失给 CommonJS 使用者。为了允许 CommonJS 使用者继续访问命名导出,模块可以确保默认导出是一个对象,并将命名导出作为属性附加到它。例如,在上面的示例中,distance
可以作为静态方法附加到默认导出 Point
类。
export function distance(a, b) {
return (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()
执行过程的伪代码高级算法:
从路径 Y 的模块中 require(X)
1. 如果 X 是核心模块,
a. 返回核心模块
b. 停止
2. 如果 X 以 '/' 开头
a. 将 Y 设置为文件系统根目录
3. 如果 X 以 './' 或 '/' 或 '../' 开头
a. LOAD_AS_FILE(Y + X)
b. LOAD_AS_DIRECTORY(Y + X)
c. 抛出 "未找到"
4. 如果 X 以 '#' 开头
a. LOAD_PACKAGE_IMPORTS(X, dirname(Y))
5. LOAD_PACKAGE_SELF(X, dirname(Y))
6. LOAD_NODE_MODULES(X, dirname(Y))
7. 抛出 "未找到"
MAYBE_DETECT_AND_LOAD(X)
1. 如果 X 解析为 CommonJS 模块,则将其作为 CommonJS 模块加载。停止。
2. 否则,如果 X 的源代码可以使用
<a href="esm#resolver-algorithm-specification">ESM 解析器中定义的 DETECT_MODULE_SYNTAX</a>
解析为 ECMAScript 模块,
a. 将 X 作为 ECMAScript 模块加载。停止。
3. 抛出尝试将 X 作为 CommonJS 在步骤 1 中解析时出现的 SyntaxError。停止。
LOAD_AS_FILE(X)
1. 如果 X 是文件,则根据其文件扩展名格式加载 X。停止
2. 如果 X.js 是文件,
a. 找到 X 最近的包范围 SCOPE。
b. 如果未找到范围
1. MAYBE_DETECT_AND_LOAD(X.js)
c. 如果 SCOPE/package.json 包含 "type" 字段,
1. 如果 "type" 字段为 "module",则将 X.js 作为 ECMAScript 模块加载。停止。
2. 如果 "type" 字段为 "commonjs",则将 X.js 作为 CommonJS 模块加载。停止。
d. MAYBE_DETECT_AND_LOAD(X.js)
3. 如果 X.json 是文件,则将 X.json 加载为 JavaScript 对象。停止
4. 如果 X.node 是文件,则将 X.node 加载为二进制插件。停止
LOAD_INDEX(X)
1. 如果 X/index.js 是文件
a. 找到 X 最近的包范围 SCOPE。
b. 如果未找到范围,则将 X/index.js 作为 CommonJS 模块加载。停止。
c. 如果 SCOPE/package.json 包含 "type" 字段,
1. 如果 "type" 字段为 "module",则将 X/index.js 作为 ECMAScript 模块加载。停止。
2. 否则,将 X/index.js 作为 CommonJS 模块加载。停止。
2. 如果 X/index.json 是文件,则将 X/index.json 解析为 JavaScript 对象。停止
3. 如果 X/index.node 是文件,则将 X/index.node 加载为二进制插件。停止
LOAD_AS_DIRECTORY(X)
1. 如果 X/package.json 是文件,
a. 解析 X/package.json,并查找 "main" 字段。
b. 如果 "main" 值为假,则转到步骤 2。
c. 令 M = X + (json main 字段)
d. LOAD_AS_FILE(M)
e. LOAD_INDEX(M)
f. LOAD_INDEX(X) 已弃用
g. 抛出 "未找到"
2. LOAD_INDEX(X)
LOAD_NODE_MODULES(X, START)
1. 令 DIRS = NODE_MODULES_PATHS(START)
2. 对于 DIRS 中的每个 DIR:
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",则转到 d。
b. DIR = path join(PARTS[0 .. I] + "node_modules")
c. DIRS = DIR + DIRS
d. 令 I = I - 1
5. 返回 DIRS + GLOBAL_FOLDERS
LOAD_PACKAGE_IMPORTS(X, DIR)
1. 找到 DIR 最近的包范围 SCOPE。
2. 如果未找到范围,则返回。
3. 如果 SCOPE/package.json 的 "imports" 为 null 或 undefined,则返回。
4. 如果启用了 `--experimental-require-module`
a. 令 CONDITIONS = ["node", "require", "module-sync"]
b. 否则,令 CONDITIONS = ["node", "require"]
5. 令 MATCH = PACKAGE_IMPORTS_RESOLVE(X, pathToFileURL(SCOPE),
CONDITIONS) <a href="esm#resolver-algorithm-specification">在 ESM 解析器中定义</a>。
6. RESOLVE_ESM_MATCH(MATCH)。
LOAD_PACKAGE_EXPORTS(X, DIR)
1. 尝试将 X 解释为 NAME 和 SUBPATH 的组合,其中名称可能带有 @scope/ 前缀,子路径以斜杠 (`/`) 开头。
2. 如果 X 与此模式不匹配或 DIR/NAME/package.json 不是文件,则返回。
3. 解析 DIR/NAME/package.json,并查找 "exports" 字段。
4. 如果 "exports" 为 null 或 undefined,则返回。
5. 如果启用了 `--experimental-require-module`
a. 令 CONDITIONS = ["node", "require", "module-sync"]
b. 否则,令 CONDITIONS = ["node", "require"]
6. 令 MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH,
`package.json` "exports", CONDITIONS) <a href="esm#resolver-algorithm-specification">在 ESM 解析器中定义</a>。
7. 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"])
<a href="esm#resolver-algorithm-specification">在 ESM 解析器中定义</a>。
6. RESOLVE_ESM_MATCH(MATCH)
RESOLVE_ESM_MATCH(MATCH)
1. 令 RESOLVED_PATH = fileURLToPath(MATCH)
2. 如果 RESOLVED_PATH 处的文件存在,则根据其扩展名格式加载 RESOLVED_PATH。停止
3. 抛出 "未找到"
缓存
模块在第一次加载后会被缓存。这意味着(除其他事项外)每次调用 require('foo')
都将返回完全相同的对象,如果它解析到相同的文件的话。
只要不修改 require.cache
,多次调用 require('foo')
不会导致模块代码被多次执行。这是一个重要的特性。借助它,可以返回“部分完成”的对象,从而允许加载传递依赖项,即使它们会导致循环。
要让模块多次执行代码,请导出一个函数,然后调用该函数。
模块缓存注意事项
模块是根据其解析后的文件名进行缓存的。由于模块可能会根据调用模块的位置解析到不同的文件名(从 node_modules
文件夹加载),因此不能保证 require('foo')
始终返回完全相同的对象,如果它解析到不同的文件的话。
此外,在不区分大小写的文件系统或操作系统上,不同的解析文件名可以指向同一个文件,但缓存仍然会将它们视为不同的模块,并会多次重新加载该文件。例如,require('./foo')
和 require('./FOO')
返回两个不同的对象,无论 ./foo
和 ./FOO
是否是同一个文件。
内置模块
[历史]
版本 | 变更 |
---|---|
v16.0.0, v14.18.0 | 为 require(...) 添加了 node: 导入支持。 |
Node.js 将多个模块编译到二进制文件中。这些模块在本文档的其他地方有更详细的描述。
内置模块定义在 Node.js 源代码中,位于 lib/
文件夹。
可以使用 node:
前缀标识内置模块,在这种情况下,它会绕过 require
缓存。例如,require('node:http')
将始终返回内置的 HTTP 模块,即使存在同名的 require.cache
条目。
如果将它们的标识符传递给 require()
,则某些内置模块将始终优先加载。例如,require('http')
将始终返回内置的 HTTP 模块,即使存在同名的文件。无需使用 node:
前缀即可加载的内置模块列表在 module.builtinModules
中公开,列出时不包含前缀。
带有强制性 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()
的文件。也就是说,为了使 require('./circle')
能够找到 circle.js
,circle.js
必须与 foo.js
位于同一目录中。
如果没有 '/'
、'./'
或 '../'
开头来指示文件,则该模块必须是核心模块或从 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.js
或 index.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
错误。使用包的 子路径导出 或 子路径导入 可以提供与文件夹作为模块相同的作用域组织优势,并且同时适用于 require
和 import
。
从 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
是用户的 home 目录,$PREFIX
是 Node.js 配置的 node_prefix
。
这些主要出于历史原因。
强烈建议将依赖项放在本地 node_modules
文件夹中。这些将加载更快,也更可靠。
模块包装器
在执行模块代码之前,Node.js 会使用如下所示的函数包装器将其包装:
;(function (exports, require, module, __filename, __dirname) {
// 模块代码实际位于此处
})
通过这样做,Node.js 实现了几件事:
- 它将顶层变量(使用
var
、const
或let
定义)的作用域限制在模块内,而不是全局对象。 - 它有助于提供一些实际上特定于模块的全局变量,例如:
module
和exports
对象,实现者可以使用它们从模块导出值。- 方便变量
__filename
和__dirname
,包含模块的绝对文件名和目录路径。
模块作用域
__dirname
新增于:v0.1.27
当前模块的目录名。这与 __filename
的 path.dirname()
相同。
示例:从 /Users/mjr
运行 node example.js
console.log(__dirname)
// 输出:/Users/mjr
console.log(path.dirname(__filename))
// 输出:/Users/mjr
__filename
新增于:v0.0.1
当前模块的文件名。这是当前模块文件的绝对路径,已解析符号链接。
对于主程序,这不一定与命令行中使用的文件名相同。
有关当前模块的目录名,请参阅 __dirname
。
示例:
从 /Users/mjr
运行 node example.js
console.log(__filename)
// 输出:/Users/mjr/example.js
console.log(__dirname)
// 输出:/Users/mjr
假设有两个模块:a
和 b
,其中 b
是 a
的依赖项,并且目录结构如下:
/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
新增于:v0.1.12
对 module.exports
的引用,键入更短。有关何时使用 exports
和何时使用 module.exports
的详细信息,请参阅关于 exports 快捷方式 的部分。
module
新增于:v0.1.16
当前模块的引用,参见module
对象 部分。特别是,module.exports
用于定义模块导出内容,并通过 require()
提供访问。
require(id)
新增于:v0.1.13
用于导入模块、JSON
和本地文件。模块可以从 node_modules
导入。可以使用相对于 __dirname
(如果已定义) 或当前工作目录的相对路径 (例如 ./
、./foo
、./bar/baz
、../foo
) 导入本地模块和 JSON 文件。POSIX 风格的相对路径以操作系统无关的方式解析,这意味着上述示例在 Windows 上的工作方式与在 Unix 系统上相同。
// 使用相对于 `__dirname` 或当前工作目录的路径导入本地模块。(在 Windows 上,这将解析为 .\path\myLocalModule。)
const myLocalModule = require('./path/myLocalModule')
// 导入 JSON 文件:
const jsonData = require('./path/filename.json')
// 从 node_modules 或 Node.js 内置模块导入模块:
const crypto = require('node:crypto')
require.cache
添加于:v0.3.0
模块在 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
添加于:v0.3.0
自 v0.10.6 起已弃用
指示 require
如何处理某些文件扩展名。
将扩展名为 .sjs
的文件作为 .js
处理:
require.extensions['.sjs'] = require.extensions['.js']
**已弃用。**过去,此列表用于通过按需编译将非 JavaScript 模块加载到 Node.js 中。但是,实际上,有更好的方法可以做到这一点,例如通过其他 Node.js 程序加载模块,或提前将它们编译成 JavaScript。
避免使用 require.extensions
。使用它可能会导致细微的错误,并且随着每个注册扩展名的增加,解析扩展名的速度会变慢。
require.main
新增于:v0.1.17
表示 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])
[历史]
版本 | 变更 |
---|---|
v8.9.0 | 现在支持 paths 选项。 |
v0.3.0 | 新增于:v0.3.0 |
request
<string> 要解析的模块路径。options
<Object>paths
<string[]> 用于解析模块位置的路径。如果存在,则使用这些路径代替默认的解析路径,但 GLOBAL_FOLDERS 例如$HOME/.node_modules
除外,它们始终包含在内。这些路径中的每一个都用作模块解析算法的起点,这意味着从这个位置检查node_modules
层次结构。
返回值: <string>
使用内部 require()
机制查找模块的位置,但不是加载模块,而是只返回解析后的文件名。
如果找不到模块,则抛出 MODULE_NOT_FOUND
错误。
require.resolve.paths(request)
新增于:v8.9.0
request
<string> 正在检索其查找路径的模块路径。- 返回值: <string[]> | <null>
返回一个数组,其中包含在解析 request
期间搜索的路径,如果 request
字符串引用核心模块(例如 http
或 fs
),则返回 null
。
module
对象
新增于:v0.1.16
在每个模块中,module
自由变量都是对表示当前模块的对象的引用。为方便起见,module.exports
也可通过 exports
模块全局变量访问。module
实际上并非全局变量,而是每个模块的局部变量。
module.children
新增于:v0.1.16
此模块首次需要的模块对象。
module.exports
新增于:v0.1.16
module.exports
对象由 Module
系统创建。有时这不可接受;许多人希望他们的模块成为某个类的实例。为此,将所需的导出对象赋值给 module.exports
。将所需的对象赋值给 exports
将只会重新绑定局部 exports
变量,这可能并非所需结果。
例如,假设我们正在创建一个名为 a.js
的模块:
const EventEmitter = require('node:events')
module.exports = new EventEmitter()
// 执行一些工作,并在一段时间后从模块本身发出
// 'ready' 事件。
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
快捷方式
新增于:v0.1.16
exports
变量在模块的文件级作用域内可用,并在模块求值之前被赋值为 module.exports
的值。
它允许使用快捷方式,以便可以将 module.exports.f = ...
更简洁地写成 exports.f = ...
。但是,请注意,像任何变量一样,如果为 exports
赋值一个新值,它将不再绑定到 module.exports
:
module.exports.hello = true // 从模块的 require 中导出
exports = { hello: false } // 未导出,仅在模块内可用
当 module.exports
属性被完全替换为一个新对象时,通常也需要重新赋值 exports
:
module.exports = exports = function Constructor() {
// ... 等
}
为了说明这种行为,想象一下 require()
的这个假设实现,它与 require()
的实际实现非常相似:
function require(/* ... */) {
const module = { exports: {} }
;((module, exports) => {
// 模块代码在此。在这个例子中,定义一个函数。
function someFunc() {}
exports = someFunc
// 在这一点上,exports 不再是 module.exports 的快捷方式,并且
// 此模块仍然会导出一个空的默认对象。
module.exports = someFunc
// 在这一点上,模块现在将导出 someFunc,而不是
// 默认对象。
})(module, module.exports)
return module.exports
}
module.filename
新增于:v0.1.16
模块的完整解析文件名。
module.id
新增于:v0.1.16
模块的标识符。通常情况下,这是完整解析的文件名。
module.isPreloading
新增于:v15.4.0, v14.17.0
- 类型: <布尔值> 如果模块在 Node.js 预加载阶段运行,则为
true
。
module.loaded
新增于:v0.1.16
模块是否已完成加载,或者正在加载中。
module.parent
新增于:v0.1.16
自 v14.6.0, v12.19.0 起已弃用
[稳定性: 0 - 已弃用]
稳定性: 0 稳定性: 0 - 已弃用:请改用 require.main
和 module.children
。
首先需要此模块的模块,如果当前模块是当前进程的入口点,则为 null
,如果模块是由非 CommonJS 模块(例如:REPL 或 import
)加载的,则为 undefined
。
module.path
新增于:v11.14.0
模块的目录名。这通常与 path.dirname()
的 module.id
相同。
module.paths
新增于:v0.4.0
模块的搜索路径。
module.require(id)
新增于:v0.5.1
module.require()
方法提供了一种加载模块的方式,就像从原始模块调用 require()
一样。
为此,需要获取对 module
对象的引用。由于 require()
返回 module.exports
,并且 module
通常只在特定模块的代码中可用,因此必须显式导出它才能使用。
Module
对象
本节内容已移至 模块:module
核心模块。
源映射 v3 支持
本节内容已移至 模块:module
核心模块。