CommonJS 模块
在 Node.js 中,每个文件都被视为一个单独的模块。
CommonJS 模块系统在 module 核心模块中实现。
启用
Node.js 有两个模块系统:CommonJS 模块和 ECMAScript 模块。
默认情况下,Node.js 会将以下内容视为 CommonJS 模块:
-
扩展名为
.cjs的文件; - 当最近的父
package.json文件包含值为"commonjs"的顶层字段"type"时,则扩展名为.js的文件。 - 当最近的父
package.json文件不包含顶层字段"type"时,则扩展名为.js的文件。 包作者应该包括"type"字段,即使在所有源都是 CommonJS 的包中也是如此。 明确包的type将使构建工具和加载器更容易确定包中的文件应该如何解释。 - 扩展名不是
.mjs、.cjs、.json、.node、或.js的文件(当最近的父package.json文件包含值为"module"的顶层字段"type"时,这些文件只有在它们是require的,而不是用作程序的命令行入口点)。
调用 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 交互式解释器,将 /usr/lib/node_modules 文件夹添加到 $NODE_PATH 环境变量可能会很有用。 由于使用 node_modules 文件夹的模块查找都是相对的,并且基于调用 require() 的文件的真实路径,因此包本身可以位于任何位置。
总结
要获取调用 require() 时将加载的确切文件名,则使用 require.resolve() 函数。
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 be the filesystem root 3. If X begins with './' or '/' 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" 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 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. 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" 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. 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. let MATCH = PACKAGE_IMPORTS_RESOLVE(X, pathToFileURL(SCOPE), ["node", "require"]) defined in the ESM resolver. 5. 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. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH, `package.json` "exports", ["node", "require"]) defined in the ESM resolver. 6. 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"]) defined in the ESM resolver. 6. RESOLVE_ESM_MATCH(MATCH) RESOLVE_ESM_MATCH(MATCH) 1. let { RESOLVED, EXACT } = MATCH 2. let RESOLVED_PATH = fileURLToPath(RESOLVED) 3. If EXACT is true, a. If the file at RESOLVED_PATH exists, load RESOLVED_PATH as its extension format. STOP 4. Otherwise, if EXACT is false, a. LOAD_AS_FILE(RESOLVED_PATH) b. LOAD_AS_DIRECTORY(RESOLVED_PATH) 5. THROW "not found"
缓存
模块在第一次加载后被缓存。 这意味着(类似其他缓存)每次调用 require('foo') 都会返回完全相同的对象(如果解析为相同的文件)。
如果 require.cache 没有被修改,则多次调用 require('foo') 不会导致模块代码被多次执行。 这是重要的特征。 有了它,可以返回“部分完成”的对象,从而允许加载传递依赖项,即使它们会导致循环。
要让模块多次执行代码,则导出函数,然后调用该函数。
模块缓存的注意事项
模块根据其解析的文件名进行缓存。 由于模块可能会根据调用模块的位置(从 node_modules 文件夹加载)解析为不同的文件名,因此如果 require('foo') 解析为不同的文件,则不能保证 require('foo') 将始终返回完全相同的对象。
此外,在不区分大小写的文件系统或操作系统上,不同的解析文件名可以指向同一个文件,但缓存仍会将它们视为不同的模块,并将多次重新加载文件。 例如,require('./foo') 和 require('./FOO') 返回两个不同的对象,而不管 ./foo 和 ./FOO 是否是同一个文件。
核心模块
可以使用 node: 前缀来识别核心模块,在这种情况下它会绕过 require 缓存。 例如,require('node:http') 将始终返回内置的 HTTP 模块,即使有该名称的 require.cache 条目。
如果某些核心模块的标识符传给 require(),则总是优先加载它们。 例如,require('http') 将始终返回内置的 HTTP 模块,即使存在该名称的文件。 不使用 node: 前缀可以加载的核心模块列表暴露为 module.builtinModules。
循环
当有循环 require() 调用时,模块在返回时可能尚未完成执行。a.js里require(b.js),b.js里require(a.js),这种情况是循环。
文件模块
如果找不到确切的文件名,Node.js 将尝试加载所需的文件名,并添加扩展名:.js、.json,最后是 .node。 当加载具有不同扩展名的文件(例如 .cjs)时,则必须将其全名传给 require(),包括其文件扩展名(例如 require('./file.cjs'))
.json 文件被解析为 JSON 文本文件,.node 文件被解释为加载了 process.dlopen() 的已编译插件模块。使用任何其他扩展名(或根本没有扩展名)的文件被解析为 JavaScript 文本文件。
以 '/' 为前缀的必需模块是文件的绝对路径。
以 './' 为前缀的必需模块与调用 require() 的文件相关。
如果没有前导 '/'、'./' 或 '../' 来指示文件,则该模块必须是核心模块或从 node_modules 文件夹加载。
如果给定路径不存在,则 require() 将抛出 MODULE_NOT_FOUND 错误。
目录作为模块
可以通过三种方式将文件夹作为参数传给 require()。
// 文件夹some-library中有package.json文件,并且内容如下所示
{ "name" : "some-library",
"main" : "./lib/some-library.js" }
则 require('./some-library') 将尝试加载 ./some-library/lib/some-library.js
如果目录中没有package.json或者没有main或无法解析,则require('./some-library') 将尝试加载:
./some-library/index.js./some-library/index.node
如果上面都找不到,则报错:Error: Cannot find module 'some-library'
从 node_modules 目录加载
如果传给 require() 的模块标识符不是核心模块,并且不以 '/'、'../' 或 './' 开头,则 Node.js 从当前模块的目录开始,并添加 /node_modules,并尝试从该位置加载模块。 Node.js 不会将 node_modules 附加到已经以 node_modules 结尾的路径。
如果在那里找不到它,则它移动到父目录,依此类推,直到到达文件系统的根目录。
通过在模块名称后包含路径后缀,可以要求与模块一起分发的特定文件或子模块。 例如,require('example-module/path/to/file') 将相对于 example-module 所在的位置解析 path/to/file。 后缀路径遵循相同的模块解析语义。
注意:require('example-module/path/to/file') 做一下测试,最后的file取的是node_modules中的example-module/path/to/file.js或者file.json或者file.node?如果都没有取到,会从example-module/path/to/node-module/file.js取么? 照理说一般情况下,file.js都会有的。
从全局目录加载
如果 NODE_PATH 环境变量设置为以冒号分隔的绝对路径列表,则 Node.js 将在这些路径中搜索模块(如果它们在其他地方找不到)。
模块封装器
在执行模块代码之前,Node.js 将使用如下所示的函数封装器对其进行封装:
(function(exports, require, module, __filename, __dirname) {
// 模块代码实际存在于此处
});
通过这样做,Node.js 实现了以下几点:
- 它将顶层变量(使用
var、const或let定义)保持在模块而不是全局对象的范围内。 - 它有助于提供一些实际特定于模块的全局变量,例如:
module和exports对象,实现者可以用来从模块中导出值。- 便利变量
__filename和__dirname,包含模块的绝对文件名和目录路径。
模块作用域
__dirname
当前模块的目录名。 这与 __filename 的 path.dirname() 相同。
console.log(__dirname);
// 打印: /Users/mjr
console.log(path.dirname(__filename));
// 打印: /Users/mjr
__filename
当前模块的文件名。 这是当前模块文件的已解析符号链接的绝对路径。
console.log(__filename);
// 打印: /Users/mjr/example.js
console.log(__dirname);
// 打印: /Users/mjr
exports
对 module.exports 的引用,其输入更短。 有关何时使用 exports 和何时使用 module.exports 的详细信息,请参阅有关导出的快捷方式的章节。
module
对当前模块的引用,请参阅有关 module 对象的部分。 特别是,module.exports 用于定义模块通过 require() 导出和提供的内容。
require(id)
用于导入模块、JSON 和本地文件。
require.cache
模块在需要时缓存在此对象中。 通过从此对象中删除键值,下一次 require 将重新加载模块。 这不适用于原生插件,因为重新加载会导致错误。
const assert = require('node:assert');
const realFs = require('node:fs');
const fakeFs = {};
require.cache.fs = { exports: fakeFs };
assert.strictEqual(require('node:fs'), fakeFs);
assert.strictEqual(require('node:fs'), realFs);
require.main
Module 对象代表 Node.js 进程启动时加载的入口脚本,如果程序的入口点不是 CommonJS 模块,则为 undefined。
在 entry.js 脚本中:
// 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 字符串引用核心模块,例如 http 或 fs,则返回包含在解析 request 或 null 期间搜索的路径的数组。
module 对象
在每个模块中,module 自由变量是对代表当前模块的对象的引用。 为方便起见,module.exports 也可通过 exports 模块全局访问
module.children
这个模块首次需要的对象。 require() 需要的对象
赋值给 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.hello = true; // 从模块的 require 中导出
exports = { hello: false }; // 未导出,仅在模块中可用
module.filename
模块的完全解析文件名。
module.id
模块的标识符。 通常这是完全解析的文件名。
module.isPreloading
- 类型: <boolean> 如果模块在 Node.js 预加载阶段运行,则为
true。
module.loaded
模块是否已完成加载,或正在加载。
module.path
模块的目录名称。 这通常与 module.id 的 path.dirname() 相同。
module.paths
模块的搜索路径。
module.require(id)
- 返回: <any> 导出的模块内容
module.require() 方法提供了一种加载模块的方法,就像从原始模块调用 require() 一样。

浙公网安备 33010602011771号