首页 > 其他分享 >CommonJS 模块

CommonJS 模块

时间:2023-12-11 10:56:25浏览次数:38  
标签:node CommonJS require exports module js 模块

在 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 实现了以下几点:

  • 它将顶层变量(使用 varconst 或 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

相关文章

  • 前端歌谣-第五拾一课-node之http模块之stream流
    前言我是歌谣微信公众号关注前端小歌谣一起学习前端知识今天继续给大家讲解node中stream模块的讲解案例constfs=require("fs")constrs=fs.createReadStream("./1.txt","utf-8")rs.on("data",(chunk)=>{console.log(chunk)})rs.on("end",()=>{......
  • 前端歌谣-第五十课-node之http模块之fs模块(续)
    前言我是歌谣微信公众号关注前端小歌谣一起学习前端知识今天继续给大家讲解node中fs模块的讲解同步创建constfs=require("fs")fs.mkdirSync("./geyao1",(err)=>{console.log(err)if(err&&err.code==="EEXIST"){console.log("目录已经存在")}})运行......
  • day18 hash logging模块
    day182023年12月9日周六14:03:43day17复习datetime.datetime.now()要什么文件切割就可以random.choice([1,2,3])随机选择random.shuffle()打乱顺序random.random(1,2)随机取数os.mkdir()新建一个文件夹os模块与操作系统交互操作文件和文件夹sys与py解释器交互环境变量......
  • 自动驾驶的关键子模块
    本文参考:基于深度学习的端到端自动驾驶最新综述2311.18636.pdf(arxiv.org)自动驾驶软件的一些关键子模块包括:地图绘制和高精地图绘制:是指创建和维护道路和周围环境的详细、高精地图。与人类驾驶员使用的传统导航地图不同,自动驾驶地图要详细得多,并包含对自动驾驶系统的特......
  • Python 时间处理与时区转换:深入探究 datetime、time 模块与 pytz 库的功能与应用
    Python中的datetime和time模块为处理时间和日期提供了强大的功能。这些模块不仅支持时间和日期的操作,还能进行时间戳的转换、时区操作等。在本文中,我们将深入介绍这些模块的用法和实际示例。1.datetime模块:处理日期和时间datetime模块是Python标准库中用于处理日期和时间......
  • 前端歌谣-第四拾九课-node之http模块之fs模块
    前言我是歌谣微信公众号关注前端小歌谣一起学习前端知识今天继续给大家讲解node中fs模块的讲解创建文件constfs=require("fs")fs.mkdir("./geyao",(err)=>{console.log(err)if(err&&err.code==="EEXIST"){console.log("目录已经存在")}})运行结果重命......
  • 前端歌谣-第四拾捌课-node之http模块之event模块
    前言我是歌谣微信公众号关注前端小歌谣一起学习前端知识今天继续给大家讲解node中event的讲解案例constEventEmitter=require("events")constevent=newEventEmitter()event.on("play",()=>{console.log("事件触发了")})event.emit("play")运行结果案例1varhttp=r......
  • 如何设置div内的模块靠左显示,模块内容按行显示?
    要设置一个div内的模块靠左显示,并且模块内容按行显示,你可以使用CSS中的flexbox布局来实现。以下是一种可能的解决方案:HTML结构:<divclass="container"><divclass="module">模块1</div><divclass="module">模块2</div><divclass="module"&g......
  • 7、i2c模块
    i2ci2c中发出时钟信号的是主机。主机通过地址来访问从机。发送数据到总线的叫发送器,接受数据的器件叫接收器。在物理结构上,i2c由一条串行总线SDA和一条串行时钟总线SCL构成。I2C基本编程步骤:初始化时钟、配置引脚、起始信号、读、写、终止信号起始信号(一般由主机产生)起始信......
  • 6、spi模块
    串行外设接口SPI模块spi分为主从两种模式,一个spi通讯系统要包含一个主设备和一个或多个从设备。提供时钟的是主设备。spi的读写操作都是从主设备发起的。SPI信号线SPI接口一般使用四条信号线通信:SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)MISO:主设备输入/从设备输出引脚。该引......