添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

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 文件包含值为 "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_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 交互式解释器,将 /usr/lib/node_modules 文件夹添加到 $NODE_PATH 环境变量可能会很有用。 由于使用 node_modules 文件夹的模块查找都是相对的,并且基于调用 require() 的文件的真实路径,因此包本身可以位于任何位置。

    .mjs 扩展名#

    ERR_REQUIRE_ESM 错误。 改用 import()

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

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

    综上所述,这里是 require() 的伪代码高级算法:

     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') 不会导致模块代码被多次执行。 这是重要的特征。 有了它,可以返回“部分完成”的对象,从而允许加载传递依赖项,即使它们会导致循环。

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

    模块缓存的注意事项#

    #

    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 导出对象的未完成副本返回给 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

    需要仔细规划以允许循环模块依赖项在应用程序中正常工作。

    文件模块#

    确定模块系统章节以了解将使用什么解析目标。

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

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

    如果没有前导 '/''./''../' 来指示文件,则该模块必须是核心模块或从 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.jsindex.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 错误。 使用包子路径导出子路径导入可以提供与文件夹作为模块相同的包含组织优势,并且适用于 requireimport

    从 node_modules 目录加载#

    核心模块,并且不以 '/''../''./' 开头,则 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_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 是用户的主目录,$PREFIX 是 Node.js 配置的 node_prefix

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

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

    模块封装器#

    #

    #

  • <string>
  • 当前模块的目录名。 这与 __filenamepath.dirname() 相同。

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

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

    __filename#

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

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

    当前模块的目录名见 __dirname

    /Users/mjr 运行 node example.js

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

    给定两个模块:ab,其中 ba 的依赖项,且目录结构为:

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

  • <Object>
  • module.exports 的引用,其输入更短。 有关何时使用 exports 和何时使用 module.exports 的详细信息,请参阅有关导出的快捷方式的章节。

    module#

  • <module>
  • 对当前模块的引用,请参阅有关 module 对象的部分。 特别是,module.exports 用于定义模块通过 require() 导出和提供的内容。

    require(id)#

  • <string> 模块名称或路径
  • 返回: <any> 导出的模块内容
  • 用于导入模块、JSON 和本地文件。 模块可以从 node_modules 导入。 可以使用相对路径(例如 ././foo./bar/baz../foo)导入本地模块和 JSON 文件,该路径将根据 __dirname(如果有定义)命名的目录或当前工作目录进行解析。 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#

  • <Object>
  • 模块在需要时缓存在此对象中。 通过从此对象中删除键值,下一次 require 将重新加载模块。 这不适用于原生插件,因为重新加载会导致错误。

    添加或替换条目也是可能的。 在内置模块之前检查此缓存,如果将与内置模块匹配的名称添加到缓存中,则只有 node: 前缀的 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.extensions#

    稳定性: 0 - 弃用

  • <Object>
  • 指导 require 如何处理某些文件扩展名。

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

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

    已弃用。 过去,此列表用于通过按需编译将非 JavaScript 模块加载到 Node.js 中。 但是,在实践中,有很多更好的方法可以做到这一点,例如通过其他一些 Node.js 程序加载模块,或者提前将它们编译为 JavaScript。

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

    require.main#

  • <module> | <undefined>
  • Module 对象代表 Node.js 进程启动时加载的入口脚本,如果程序的入口点不是 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])#

  • <string[]> 从中解析模块位置的路径。 如果存在,则使用这些路径而不是默认的解析路径,除了 GLOBAL_FOLDERS(例如 $HOME/.node_modules,其总是被包含在内)。 这些路径中的每一个都用作模块解析算法的起点,这意味着从此位置检查 node_modules 层级。
  • 返回: <string>
  • 使用内部的 require() 工具查找模块的位置,但不加载模块,只返回解析的文件名。

    如果找不到模块,则会抛出 MODULE_NOT_FOUND 错误。

    require.resolve.paths(request)#

  • <string> 正在检索其查找路径的模块路径。
  • 返回: <string[]> | <null>
  • 如果 request 字符串引用核心模块,例如 httpfs,则返回包含在解析 requestnull 期间搜索的路径的数组。

    module 对象#

    #

  • <module[]>
  • 这个模块首次需要的对象。

    module.exports#

  • <Object>
  • 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);
    导出的快捷方式#

    #

  • <string>
  • 模块的完全解析文件名。

    module.id#

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

    module.isPreloading#

  • <boolean> 如果模块在 Node.js 预加载阶段运行,则为 true
  • module.loaded#

  • <boolean>
  • 模块是否已完成加载,或正在加载。

    module.parent#

    稳定性: 0 - 弃用: 改为使用 require.mainmodule.children

  • <module> | <null> | <undefined>
  • 首先需要这个模块的模块,如果当前模块是当前进程的入口点,则为 null,如果模块是由不是 CommonJS 模块的东西(例如:REPL 或 import)加载的,则为 undefined

    module.path#

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

    module.paths#

  • <string[]>
  • 模块的搜索路径。

    module.require(id)#

  • <string>
  • 返回: <any> 导出的模块内容
  • module.require() 方法提供了一种加载模块的方法,就像从原始模块调用 require() 一样。

    为此,必须获得对 module 对象的引用。 由于 require() 返回 module.exports,而 module 通常仅在特定模块的代码中可用,因此必须明确导出才能使用。

    Module 对象#

    模块:module 核心模块

  • module.builtinModules
  • module.createRequire(filename)
  • module.syncBuiltinESMExports()
  • Source Map V3 的支持#

    模块:module 核心模块

  • module.findSourceMap(path)
  • module.SourceMap
  • new SourceMap(payload)
  • sourceMap.payload
  • sourceMap.findEntry(lineNumber, columnNumber)
  •