在 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()
一样。
标签:node,CommonJS,require,exports,module,js,模块 From: https://www.cnblogs.com/withheart/p/17893872.html