Node.js v22.12.0 文档


模块:包#

¥Modules: Packages

介绍#

¥Introduction

包是由 package.json 文件描述的文件夹树。包由包含 package.json 文件的文件夹和所有子文件夹组成,直到包含另一个 package.json 文件的下一个文件夹或名为 node_modules 的文件夹。

¥A package is a folder tree described by a package.json file. The package consists of the folder containing the package.json file and all subfolders until the next folder containing another package.json file, or a folder named node_modules.

此页面为编写 package.json 文件的包作者提供指导,以及 Node.js 定义的 package.json 字段的参考。

¥This page provides guidance for package authors writing package.json files along with a reference for the package.json fields defined by Node.js.

确定模块系统#

¥Determining module system

介绍#

¥Introduction

当传递给 node 作为初始输入时,或者当被 import 语句或 import() 表达式引用时,Node.js 会将以下内容视为 ES 模块

¥Node.js will treat the following as ES modules when passed to node as the initial input, or when referenced by import statements or import() expressions:

  • 扩展名为 .mjs 的文件。

    ¥Files with an .mjs extension.

  • 当最近的父 package.json 文件包含值为 "module" 的顶层 "type" 字段时,扩展名为 .js 的文件。

    ¥Files with a .js extension when the nearest parent package.json file contains a top-level "type" field with a value of "module".

  • 字符串作为参数传入 --eval,或通过 STDIN 管道传输到 node,带有标志 --input-type=module

    ¥Strings passed in as an argument to --eval, or piped to node via STDIN, with the flag --input-type=module.

  • 代码包含仅成功解析为 ES 模块 的语法,例如 importexport 语句或 import.meta,没有明确标记应如何解释。显式标记是 .mjs.cjs 扩展、带有 "module""commonjs" 值的 package.json "type" 字段,或者 --input-type--experimental-default-type 标志。CommonJS 或 ES 模块均支持动态 import() 表达式,并且不会强制将文件视为 ES 模块。参见 语法检测

    ¥Code containing syntax only successfully parsed as ES modules, such as import or export statements or import.meta, with no explicit marker of how it should be interpreted. Explicit markers are .mjs or .cjs extensions, package.json "type" fields with either "module" or "commonjs" values, or --input-type or --experimental-default-type flags. Dynamic import() expressions are supported in either CommonJS or ES modules and would not force a file to be treated as an ES module. See Syntax detection.

当传递给 node 作为初始输入时,或者当被 import 语句或 import() 表达式引用时,Node.js 会将以下内容视为 CommonJS

¥Node.js will treat the following as CommonJS when passed to node as the initial input, or when referenced by import statements or import() expressions:

  • 扩展名为 .cjs 的文件。

    ¥Files with a .cjs extension.

  • 当最近的父 package.json 文件包含值为 "commonjs" 的顶层字段 "type" 时,则扩展名为 .js 的文件。

    ¥Files with a .js extension when the nearest parent package.json file contains a top-level field "type" with a value of "commonjs".

  • 字符串作为参数传入 --eval--print,或通过 STDIN 管道传输到 node,带有标志 --input-type=commonjs

    ¥Strings passed in as an argument to --eval or --print, or piped to node via STDIN, with the flag --input-type=commonjs.

除了这些明确的情况之外,还有其他情况,Node.js 根据 --experimental-default-type 标志的值默认使用一个模块系统或另一个模块系统:

¥Aside from these explicit cases, there are other cases where Node.js defaults to one module system or the other based on the value of the --experimental-default-type flag:

  • 如果同一文件夹或任何父文件夹中不存在 package.json 文件,则以 .js 结尾或没有扩展名的文件。

    ¥Files ending in .js or with no extension, if there is no package.json file present in the same folder or any parent folder.

  • 如果最近的父 package.json 字段缺少 "type" 字段,则以 .js 结尾或没有扩展名的文件;除非该文件夹位于 node_modules 文件夹内。(当 package.json 文件缺少 "type" 字段时,无论 --experimental-default-type 如何,为了向后兼容,node_modules 下的包范围始终被视为 CommonJS。)

    ¥Files ending in .js or with no extension, if the nearest parent package.json field lacks a "type" field; unless the folder is inside a node_modules folder. (Package scopes under node_modules are always treated as CommonJS when the package.json file lacks a "type" field, regardless of --experimental-default-type, for backward compatibility.)

  • 当未指定 --input-type 时,字符串作为参数传递给 --eval 或通过 STDIN 通过管道传递给 node

    ¥Strings passed in as an argument to --eval or piped to node via STDIN, when --input-type is unspecified.

该标志当前默认为 "commonjs",但将来可能会更改为默认为 "module"。因此,最好尽可能明确;特别是,包作者应始终在其 package.json 文件中包含 "type" 字段,即使在所有源都是 CommonJS 的包中也是如此。如果 Node.js 的默认类型发生变化,显式说明包的 type 将使包面向未来,它还将使构建工具和加载器更容易确定应如何解释包中的文件。

¥This flag currently defaults to "commonjs", but it may change in the future to default to "module". For this reason it is best to be explicit wherever possible; in particular, package authors should always include the "type" field in their package.json files, even in packages where all sources are CommonJS. Being explicit about the type of the package will future-proof the package in case the default type of Node.js ever changes, and it will also make things easier for build tools and loaders to determine how the files in the package should be interpreted.

语法检测#

¥Syntax detection

稳定性: 1.2 - 发布候选

¥Stability: 1.2 - Release candidate

Node.js 会检查不明确输入的源代码以确定其是否包含 ES 模块语法;如果检测到这样的语法,输入将被视为 ES 模块。

¥Node.js will inspect the source code of ambiguous input to determine whether it contains ES module syntax; if such syntax is detected, the input will be treated as an ES module.

不明确的输入定义为:

¥Ambiguous input is defined as:

  • 带有 .js 扩展名或没有扩展名的文件;并且要么没有控制 package.json 文件,要么缺少 type 字段;且未指定 --experimental-default-type

    ¥Files with a .js extension or no extension; and either no controlling package.json file or one that lacks a type field; and --experimental-default-type is not specified.

  • 当未指定 --input-type--experimental-default-type 时,字符串输入(--eval 或 STDIN)。

    ¥String input (--eval or STDIN) when neither --input-type nor --experimental-default-type are specified.

ES 模块语法被定义为当评估为 CommonJS 时会抛出异常的语法。这包括以下内容:

¥ES module syntax is defined as syntax that would throw when evaluated as CommonJS. This includes the following:

  • import 语句(但不是 import() 表达式,它们在 CommonJS 中有效)。

    ¥import statements (but not import() expressions, which are valid in CommonJS).

  • export 声明。

    ¥export statements.

  • import.meta 参考文献。

    ¥import.meta references.

  • await 位于模块的顶层。

    ¥await at the top level of a module.

  • CommonJS 封装器变量的词法重新声明(requiremoduleexports__dirname__filename)。

    ¥Lexical redeclarations of the CommonJS wrapper variables (require, module, exports, __dirname, __filename).

模块加载器#

¥Modules loaders

Node.js 有两个系统用于解析说明符和加载模块。

¥Node.js has two systems for resolving a specifier and loading modules.

有 CommonJS 模块加载器:

¥There is the CommonJS module loader:

  • 它是完全同步的。

    ¥It is fully synchronous.

  • 它负责处理 require() 调用。

    ¥It is responsible for handling require() calls.

  • 它是可修补的。

    ¥It is monkey patchable.

  • 它支持 文件夹作为模块

    ¥It supports folders as modules.

  • 解析说明符时,如果未找到完全匹配项,它将尝试添加扩展名(.js.json,最后是 .node),然后尝试解析 文件夹作为模块

    ¥When resolving a specifier, if no exact match is found, it will try to add extensions (.js, .json, and finally .node) and then attempt to resolve folders as modules.

  • 它将 .json 视为 JSON 文本文件。

    ¥It treats .json as JSON text files.

  • .node 文件被解释为加载了 process.dlopen() 的编译插件模块。

    ¥.node files are interpreted as compiled addon modules loaded with process.dlopen().

  • 它将所有缺少 .json.node 扩展名的文件视为 JavaScript 文本文件。

    ¥It treats all files that lack .json or .node extensions as JavaScript text files.

  • 如果模块图是同步的(不包含顶层 await),它只能用于 从 CommonJS 模块加载 ECMASCript 模块。当用于加载不是 ECMAScript 模块的 JavaScript 文本文件时,该文件将作为 CommonJS 模块加载。

    ¥It can only be used to load ECMASCript modules from CommonJS modules if the module graph is synchronous (that contains no top-level await). When used to load a JavaScript text file that is not an ECMAScript module, the file will be loaded as a CommonJS module.

有 ECMAScript 模块加载器:

¥There is the ECMAScript module loader:

  • 它是异步的,除非它用于加载 require() 的模块。

    ¥It is asynchronous, unless it's being used to load modules for require().

  • 负责处理 import 语句和 import() 表达式。

    ¥It is responsible for handling import statements and import() expressions.

  • 它不是猴子可修补的,可以使用 加载器钩子 进行定制。

    ¥It is not monkey patchable, can be customized using loader hooks.

  • 它不支持文件夹作为模块,必须完全指定目录索引(例如 './startup/index.js')。

    ¥It does not support folders as modules, directory indexes (e.g. './startup/index.js') must be fully specified.

  • 它不进行扩展名搜索。当说明符是相对或绝对的文件 URL 时,必须提供文件扩展名。

    ¥It does no extension searching. A file extension must be provided when the specifier is a relative or absolute file URL.

  • 它可以加载 JSON 模块,但需要导入类型属性。

    ¥It can load JSON modules, but an import type attribute is required.

  • 它只接受 JavaScript 文本文件的 .js.mjs.cjs 扩展名。

    ¥It accepts only .js, .mjs, and .cjs extensions for JavaScript text files.

  • 它可以用来加载 JavaScript CommonJS 模块。这样的模块通过 cjs-module-lexer 来尝试识别命名的导出,如果可以通过静态分析确定的话是可用的。导入的 CommonJS 模块将其 URL 转换为绝对路径,然后通过 CommonJS 模块加载器加载。

    ¥It can be used to load JavaScript CommonJS modules. Such modules are passed through the cjs-module-lexer to try to identify named exports, which are available if they can be determined through static analysis. Imported CommonJS modules have their URLs converted to absolute paths and are then loaded via the CommonJS module loader.

package.json 和文件扩展名#

¥package.json and file extensions

在包中,package.json "type" 字段定义了 Node.js 应该如何解释 .js 文件。如果 package.json 文件没有 "type" 字段,则 .js 文件将被视为 CommonJS

¥Within a package, the package.json "type" field defines how Node.js should interpret .js files. If a package.json file does not have a "type" field, .js files are treated as CommonJS.

"module"package.json "type" 值告诉 Node.js 将该包中的 .js 文件解释为使用 ES 模块 语法。

¥A package.json "type" value of "module" tells Node.js to interpret .js files within that package as using ES module syntax.

"type" 字段不仅适用于初始入口点 (node my-app.js),还适用于 import 语句和 import() 表达式引用的文件。

¥The "type" field applies not only to initial entry points (node my-app.js) but also to files referenced by import statements and import() expressions.

// my-app.js, treated as an ES module because there is a package.json
// file in the same folder with "type": "module".

import './startup/init.js';
// Loaded as ES module since ./startup contains no package.json file,
// and therefore inherits the "type" value from one level up.

import 'commonjs-package';
// Loaded as CommonJS since ./node_modules/commonjs-package/package.json
// lacks a "type" field or contains "type": "commonjs".

import './node_modules/commonjs-package/index.js';
// Loaded as CommonJS since ./node_modules/commonjs-package/package.json
// lacks a "type" field or contains "type": "commonjs". 

.mjs 结尾的文件总是加载为 ES 模块,而不管最近的父级 package.json

¥Files ending with .mjs are always loaded as ES modules regardless of the nearest parent package.json.

.cjs 结尾的文件总是加载为 CommonJS,而不管最近的父级 package.json

¥Files ending with .cjs are always loaded as CommonJS regardless of the nearest parent package.json.

import './legacy-file.cjs';
// Loaded as CommonJS since .cjs is always loaded as CommonJS.

import 'commonjs-package/src/index.mjs';
// Loaded as ES module since .mjs is always loaded as ES module. 

.mjs.cjs 扩展可用于在同一个包中混合类型:

¥The .mjs and .cjs extensions can be used to mix types within the same package:

  • "type": "module" 包中,可以指示 Node.js 通过将特定文件命名为 .cjs 扩展名将其解释为 CommonJS(因为 .js.mjs 文件都被视为 "module" 包中的 ES 模块)。

    ¥Within a "type": "module" package, Node.js can be instructed to interpret a particular file as CommonJS by naming it with a .cjs extension (since both .js and .mjs files are treated as ES modules within a "module" package).

  • "type": "commonjs" 包中,可以指示 Node.js 将特定文件解释为 ES 模块,方法是将其命名为 .mjs 扩展名(因为 .js.cjs 文件在 "commonjs" 包中都被视为 CommonJS)。

    ¥Within a "type": "commonjs" package, Node.js can be instructed to interpret a particular file as an ES module by naming it with an .mjs extension (since both .js and .cjs files are treated as CommonJS within a "commonjs" package).

--input-type 标志#

¥--input-type flag

当设置 --input-type=module 标志时,作为参数传递给 --eval(或 -e)或通过 STDIN 传输到 node 的字符串将被视为 ES 模块

¥Strings passed in as an argument to --eval (or -e), or piped to node via STDIN, are treated as ES modules when the --input-type=module flag is set.

node --input-type=module --eval "import { sep } from 'node:path'; console.log(sep);"

echo "import { sep } from 'node:path'; console.log(sep);" | node --input-type=module 

为了完整起见,还有 --input-type=commonjs,用于显式地将字符串输入作为 CommonJS 运行。如果未指定 --input-type,这是默认行为。

¥For completeness there is also --input-type=commonjs, for explicitly running string input as CommonJS. This is the default behavior if --input-type is unspecified.

确定包管理器#

¥Determining package manager

稳定性: 1 - 实验性的

¥Stability: 1 - Experimental

虽然所有 Node.js 项目在发布后都可以由所有包管理器安装,但他们的开发团队通常需要使用特定的包管理器。为了使这个过程更容易,Node.js 附带了一个名为 Corepack 的工具,旨在使所有包管理器在你的环境中透明地可用 - 前提是你安装了 Node.js。

¥While all Node.js projects are expected to be installable by all package managers once published, their development teams are often required to use one specific package manager. To make this process easier, Node.js ships with a tool called Corepack that aims to make all package managers transparently available in your environment - provided you have Node.js installed.

默认情况下,Corepack 不会强制执行任何特定的包管理器,并将使用与每个 Node.js 版本关联的通用 "最后一次为人所知" 版本,但你可以通过在项目的 package.json 中设置 "packageManager" 字段来改善这种体验。

¥By default Corepack won't enforce any specific package manager and will use the generic "Last Known Good" versions associated with each Node.js release, but you can improve this experience by setting the "packageManager" field in your project's package.json.

包入口点#

¥Package entry points

在包的 package.json 文件中,两个字段可以定义包的入口点:"main""exports"。这两个字段都适用于 ES 模块和 CommonJS 模块入口点。

¥In a package's package.json file, two fields can define entry points for a package: "main" and "exports". Both fields apply to both ES module and CommonJS module entry points.

所有版本的 Node.js 都支持 "main" 字段,但其功能有限:它只定义包的主要入口点。

¥The "main" field is supported in all versions of Node.js, but its capabilities are limited: it only defines the main entry point of the package.

"exports" 提供了 "main" 的现代替代方案,允许定义多个入口点、环境之间的条件入口解析支持,并防止除 "exports" 中定义的入口点之外的任何其他入口点。此封装允许模块作者清楚地为他们的包定义公共接口。

¥The "exports" provides a modern alternative to "main" allowing multiple entry points to be defined, conditional entry resolution support between environments, and preventing any other entry points besides those defined in "exports". This encapsulation allows module authors to clearly define the public interface for their package.

对于针对当前支持的 Node.js 版本的新包,建议使用 "exports" 字段。对于支持 Node.js 10 及以下的包,"main" 字段是必需的。如果同时定义了 "exports""main",则在支持的 Node.js 版本中,"exports" 字段优先于 "main"

¥For new packages targeting the currently supported versions of Node.js, the "exports" field is recommended. For packages supporting Node.js 10 and below, the "main" field is required. If both "exports" and "main" are defined, the "exports" field takes precedence over "main" in supported versions of Node.js.

条件导出 可以在 "exports" 中使用,为每个环境定义不同的包入口点,包括包是通过 require 还是通过 import 引用。有关在单个包中同时支持 CommonJS 和 ES 模块的更多信息,请参阅 双 CommonJS/ES 模块包部分

¥Conditional exports can be used within "exports" to define different package entry points per environment, including whether the package is referenced via require or via import. For more information about supporting both CommonJS and ES modules in a single package please consult the dual CommonJS/ES module packages section.

引入 "exports" 字段的现有包将阻止包的使用者使用任何未定义的入口点,包括 package.json(例如 require('your-package/package.json'))。这可能是一个突破性的变化。

¥Existing packages introducing the "exports" field will prevent consumers of the package from using any entry points that are not defined, including the package.json (e.g. require('your-package/package.json')). This will likely be a breaking change.

为了使 "exports" 的引入不会中断,请确保导出每个先前支持的入口点。最好显式指定入口点,以便明确定义包的公共 API。例如,之前导出 mainlibfeaturepackage.json 的项目可以使用以下 package.exports

¥To make the introduction of "exports" non-breaking, ensure that every previously supported entry point is exported. It is best to explicitly specify entry points so that the package's public API is well-defined. For example, a project that previously exported main, lib, feature, and the package.json could use the following package.exports:

{
  "name": "my-package",
  "exports": {
    ".": "./lib/index.js",
    "./lib": "./lib/index.js",
    "./lib/index": "./lib/index.js",
    "./lib/index.js": "./lib/index.js",
    "./feature": "./feature/index.js",
    "./feature/index": "./feature/index.js",
    "./feature/index.js": "./feature/index.js",
    "./package.json": "./package.json"
  }
} 

或者,项目可以选择使用导出模式导出带有和不带有扩展子路径的整个文件夹:

¥Alternatively a project could choose to export entire folders both with and without extensioned subpaths using export patterns:

{
  "name": "my-package",
  "exports": {
    ".": "./lib/index.js",
    "./lib": "./lib/index.js",
    "./lib/*": "./lib/*.js",
    "./lib/*.js": "./lib/*.js",
    "./feature": "./feature/index.js",
    "./feature/*": "./feature/*.js",
    "./feature/*.js": "./feature/*.js",
    "./package.json": "./package.json"
  }
} 

以上为任何次要包版本提供向后兼容性,包的未来重大更改可以适当地将导出限制为仅暴露的特定功能导出:

¥With the above providing backwards-compatibility for any minor package versions, a future major change for the package can then properly restrict the exports to only the specific feature exports exposed:

{
  "name": "my-package",
  "exports": {
    ".": "./lib/index.js",
    "./feature/*.js": "./feature/*.js",
    "./feature/internal/*": null
  }
} 

主入口点导出#

¥Main entry point export

当编写新包时,建议使用 "exports" 字段:

¥When writing a new package, it is recommended to use the "exports" field:

{
  "exports": "./index.js"
} 

定义 "exports" 字段后,包的所有子路径都将被封装,并且不再可供导入者使用。例如,require('pkg/subpath.js') 抛出 ERR_PACKAGE_PATH_NOT_EXPORTED 错误。

¥When the "exports" field is defined, all subpaths of the package are encapsulated and no longer available to importers. For example, require('pkg/subpath.js') throws an ERR_PACKAGE_PATH_NOT_EXPORTED error.

这种导出封装为工具的包接口以及处理包的 semver 升级提供了更可靠的保证。它不是一个强封装,因为直接要求包的任何绝对子路径(例如 require('/path/to/node_modules/pkg/subpath.js'))仍然会加载 subpath.js

¥This encapsulation of exports provides more reliable guarantees about package interfaces for tools and when handling semver upgrades for a package. It is not a strong encapsulation since a direct require of any absolute subpath of the package such as require('/path/to/node_modules/pkg/subpath.js') will still load subpath.js.

当前所有受支持的 Node.js 版本和现代构建工具都支持 "exports" 字段。对于使用旧版本 Node.js 或相关构建工具的项目,可以通过在指向同一模块的 "exports" 旁边包含 "main" 字段来实现兼容性:

¥All currently supported versions of Node.js and modern build tools support the "exports" field. For projects using an older version of Node.js or a related build tool, compatibility can be achieved by including the "main" field alongside "exports" pointing to the same module:

{
  "main": "./index.js",
  "exports": "./index.js"
} 

子路径导出#

¥Subpath exports

当使用 "exports" 字段时,可以通过将主入口点视为 "." 子路径来定义自定义子路径以及主入口点:

¥When using the "exports" field, custom subpaths can be defined along with the main entry point by treating the main entry point as the "." subpath:

{
  "exports": {
    ".": "./index.js",
    "./submodule.js": "./src/submodule.js"
  }
} 

现在消费者只能导入 "exports" 中定义的子路径:

¥Now only the defined subpath in "exports" can be imported by a consumer:

import submodule from 'es-module-package/submodule.js';
// Loads ./node_modules/es-module-package/src/submodule.js 

而其他子路径会出错:

¥While other subpaths will error:

import submodule from 'es-module-package/private-module.js';
// Throws ERR_PACKAGE_PATH_NOT_EXPORTED 

子路径中的扩展#

¥Extensions in subpaths

包作者应在其导出中提供扩展 (import 'pkg/subpath.js') 或无扩展 (import 'pkg/subpath') 子路径。这确保每个导出的模块只有一个子路径,以便所有依赖导入相同的一致说明符,使消费者清楚地了解包合同并简化包的子路径的完成。

¥Package authors should provide either extensioned (import 'pkg/subpath.js') or extensionless (import 'pkg/subpath') subpaths in their exports. This ensures that there is only one subpath for each exported module so that all dependents import the same consistent specifier, keeping the package contract clear for consumers and simplifying package subpath completions.

传统上,包倾向于使用无扩展名风格,它具有可读性和掩盖包中文件的真实路径的好处。

¥Traditionally, packages tended to use the extensionless style, which has the benefits of readability and of masking the true path of the file within the package.

随着 导入映射 现在为浏览器和其他 JavaScript 运行时中的包解析提供标准,使用无扩展样式可能会导致导入映射定义膨胀。显式文件扩展名可以避免此问题,方法是使导入映射尽可能利用 包文件夹映射 映射多个子路径,而不是每个包的子路径导出一个单独的映射条目。这也反映了在相对和绝对导入说明符中使用 完整说明符路径 的要求。

¥With import maps now providing a standard for package resolution in browsers and other JavaScript runtimes, using the extensionless style can result in bloated import map definitions. Explicit file extensions can avoid this issue by enabling the import map to utilize a packages folder mapping to map multiple subpaths where possible instead of a separate map entry per package subpath export. This also mirrors the requirement of using the full specifier path in relative and absolute import specifiers.

导出糖#

¥Exports sugar

如果 "." 导出是唯一的导出,则 "exports" 字段为这种情况提供了语法糖,即直接的 "exports" 字段值。

¥If the "." export is the only export, the "exports" field provides sugar for this case being the direct "exports" field value.

{
  "exports": {
    ".": "./index.js"
  }
} 

可以写成:

¥can be written:

{
  "exports": "./index.js"
} 

子路径导入#

¥Subpath imports

除了 "exports" 字段之外,还有一个包 "imports" 字段用于创建仅适用于包本身的导入说明符的私有映射。

¥In addition to the "exports" field, there is a package "imports" field to create private mappings that only apply to import specifiers from within the package itself.

"imports" 字段中的条目必须始终以 # 开头,以确保它们与外部包说明符消除歧义。

¥Entries in the "imports" field must always start with # to ensure they are disambiguated from external package specifiers.

例如,可以使用导入字段来获得内部模块条件导出的好处:

¥For example, the imports field can be used to gain the benefits of conditional exports for internal modules:

// package.json
{
  "imports": {
    "#dep": {
      "node": "dep-node-native",
      "default": "./dep-polyfill.js"
    }
  },
  "dependencies": {
    "dep-node-native": "^1.0.0"
  }
} 

其中 import '#dep' 没有得到外部包 dep-node-native 的解析(依次包括其导出),而是获取了相对于其他环境中的包的本地文件 ./dep-polyfill.js

¥where import '#dep' does not get the resolution of the external package dep-node-native (including its exports in turn), and instead gets the local file ./dep-polyfill.js relative to the package in other environments.

"exports" 字段不同,"imports" 字段允许映射到外部包。

¥Unlike the "exports" field, the "imports" field permits mapping to external packages.

导入字段的解析规则与导出字段类似。

¥The resolution rules for the imports field are otherwise analogous to the exports field.

子路径模式#

¥Subpath patterns

对于具有少量导出或导入的包,我们建议显式地列出每个导出子路径条目。但是对于具有大量子路径的包,这可能会导致 package.json 膨胀和维护问题。

¥For packages with a small number of exports or imports, we recommend explicitly listing each exports subpath entry. But for packages that have large numbers of subpaths, this might cause package.json bloat and maintenance issues.

对于这些用例,可以使用子路径导出模式:

¥For these use cases, subpath export patterns can be used instead:

// ./node_modules/es-module-package/package.json
{
  "exports": {
    "./features/*.js": "./src/features/*.js"
  },
  "imports": {
    "#internal/*.js": "./src/internal/*.js"
  }
} 

* 映射公开嵌套子路径,因为它只是字符串替换语法。

¥*** maps expose nested subpaths as it is a string replacement syntax only.**

然后,右侧 * 的所有实例都将替换为该值,包括它是否包含任何 / 分隔符。

¥All instances of * on the right hand side will then be replaced with this value, including if it contains any / separators.

import featureX from 'es-module-package/features/x.js';
// Loads ./node_modules/es-module-package/src/features/x.js

import featureY from 'es-module-package/features/y/y.js';
// Loads ./node_modules/es-module-package/src/features/y/y.js

import internalZ from '#internal/z.js';
// Loads ./node_modules/es-module-package/src/internal/z.js 

这是直接静态匹配和替换,无需对文件扩展名进行任何特殊处理。在映射两边包含 "*.js" 限制了暴露的包导出到只有 JS 文件。

¥This is a direct static matching and replacement without any special handling for file extensions. Including the "*.js" on both sides of the mapping restricts the exposed package exports to only JS files.

导出的静态可枚举属性由导出模式维护,因为可以通过将右侧目标模式视为针对包内文件列表的 ** glob 来确定包的各个导出。因为导出目标中禁止 node_modules 路径,所以这个扩展只依赖包本身的文件。

¥The property of exports being statically enumerable is maintained with exports patterns since the individual exports for a package can be determined by treating the right hand side target pattern as a ** glob against the list of files within the package. Because node_modules paths are forbidden in exports targets, this expansion is dependent on only the files of the package itself.

要从模式中排除私有子文件夹,可以使用 null 目标:

¥To exclude private subfolders from patterns, null targets can be used:

// ./node_modules/es-module-package/package.json
{
  "exports": {
    "./features/*.js": "./src/features/*.js",
    "./features/private-internal/*": null
  }
} 
import featureInternal from 'es-module-package/features/private-internal/m.js';
// Throws: ERR_PACKAGE_PATH_NOT_EXPORTED

import featureX from 'es-module-package/features/x.js';
// Loads ./node_modules/es-module-package/src/features/x.js 

条件导出#

¥Conditional exports

条件导出提供了一种根据特定条件映射到不同路径的方法。CommonJS 和 ES 模块导入都支持它们。

¥Conditional exports provide a way to map to different paths depending on certain conditions. They are supported for both CommonJS and ES module imports.

比如,包想要为 require()import 提供不同的 ES 模块导出可以这样写:

¥For example, a package that wants to provide different ES module exports for require() and import can be written:

// package.json
{
  "exports": {
    "import": "./index-module.js",
    "require": "./index-require.cjs"
  },
  "type": "module"
} 

Node.js 实现了以下条件,按从最具体到最不具体的顺序列出,因为应该定义条件:

¥Node.js implements the following conditions, listed in order from most specific to least specific as conditions should be defined:

  • "node-addons" - 类似于 "node" 并且匹配任何 Node.js 环境。此条件可用于提供使用原生 C++ 插件的入口点,而不是更通用且不依赖原生插件的入口点。这种情况可以通过 --no-addons 标志 禁用。

    ¥"node-addons" - similar to "node" and matches for any Node.js environment. This condition can be used to provide an entry point which uses native C++ addons as opposed to an entry point which is more universal and doesn't rely on native addons. This condition can be disabled via the --no-addons flag.

  • "node" - 匹配任何 Node.js 环境。可以是 CommonJS 或 ES 模块文件。在大多数情况下,没有必要显式调出 Node.js 平台。

    ¥"node" - matches for any Node.js environment. Can be a CommonJS or ES module file. In most cases explicitly calling out the Node.js platform is not necessary.

  • "import" - 当包通过 importimport() 加载时匹配,或者通过 ECMAScript 模块加载器通过任何顶层导入或解析操作加载。无论目标文件的模块格式如何,都适用。始终与 "require" 互斥。

    ¥"import" - matches when the package is loaded via import or import(), or via any top-level import or resolve operation by the ECMAScript module loader. Applies regardless of the module format of the target file. Always mutually exclusive with "require".

  • "require" - 通过 require() 加载包时匹配。引用的文件应该可以用 require() 加载,尽管无论目标文件的模块格式如何,条件都匹配。预期格式包括 CommonJS、JSON、原生插件和 ES 模块。始终与 "import" 互斥。

    ¥"require" - matches when the package is loaded via require(). The referenced file should be loadable with require() although the condition matches regardless of the module format of the target file. Expected formats include CommonJS, JSON, native addons, and ES modules. Always mutually exclusive with "import".

  • "module-sync" - 无论包是通过 importimport() 还是 require() 加载,都会匹配。格式应为 ES 模块,其模块图中不包含顶层 await - 如果是这样,当模块被 require() 化时,将抛出 ERR_REQUIRE_ASYNC_MODULE

    ¥"module-sync" - matches no matter the package is loaded via import, import() or require(). The format is expected to be ES modules that does not contain top-level await in its module graph - if it does, ERR_REQUIRE_ASYNC_MODULE will be thrown when the module is require()-ed.

  • "default" - 始终匹配的通用回退。可以是 CommonJS 或 ES 模块文件。这种情况应该总是排在最后。

    ¥"default" - the generic fallback that always matches. Can be a CommonJS or ES module file. This condition should always come last.

"exports" 对象中,键顺序很重要。在条件匹配过程中,较早的条目具有更高的优先级并优先于较晚的条目。一般规则是条件应按对象顺序从最具体到最不具体。

¥Within the "exports" object, key order is significant. During condition matching, earlier entries have higher priority and take precedence over later entries. The general rule is that conditions should be from most specific to least specific in object order.

使用 "import""require" 条件可能会导致一些危险,这些危险在 双 CommonJS/ES 模块包部分 中有进一步说明。

¥Using the "import" and "require" conditions can lead to some hazards, which are further explained in the dual CommonJS/ES module packages section.

"node-addons" 条件可用于提供使用原生 C++ 插件的入口点。但是,可以通过 --no-addons 标志 禁用此条件。当使用 "node-addons" 时,建议将 "default" 视为提供更通用入口点的增强功能,例如使用 WebAssembly 而不是原生插件。

¥The "node-addons" condition can be used to provide an entry point which uses native C++ addons. However, this condition can be disabled via the --no-addons flag. When using "node-addons", it's recommended to treat "default" as an enhancement that provides a more universal entry point, e.g. using WebAssembly instead of a native addon.

条件导出也可以扩展为导出子路径,例如:

¥Conditional exports can also be extended to exports subpaths, for example:

{
  "exports": {
    ".": "./index.js",
    "./feature.js": {
      "node": "./feature-node.js",
      "default": "./feature.js"
    }
  }
} 

定义了一个包,其中 require('pkg/feature.js')import 'pkg/feature.js' 可以在 Node.js 和其他 JS 环境之间提供不同的实现。

¥Defines a package where require('pkg/feature.js') and import 'pkg/feature.js' could provide different implementations between Node.js and other JS environments.

当使用环境分支时,总是尽可能包含 "default" 条件。提供 "default" 条件可确保任何未知的 JS 环境都能够使用此通用实现,这有助于避免这些 JS 环境必须伪装成现有环境以支持具有条件导出的包。出于这个原因,使用 "node""default" 条件分支通常比使用 "node""browser" 条件分支更可取。

¥When using environment branches, always include a "default" condition where possible. Providing a "default" condition ensures that any unknown JS environments are able to use this universal implementation, which helps avoid these JS environments from having to pretend to be existing environments in order to support packages with conditional exports. For this reason, using "node" and "default" condition branches is usually preferable to using "node" and "browser" condition branches.

嵌套条件#

¥Nested conditions

除了直接映射,Node.js 还支持嵌套条件对象。

¥In addition to direct mappings, Node.js also supports nested condition objects.

例如,要定义一个包,它只有双模式入口点用于 Node.js 而不是浏览器:

¥For example, to define a package that only has dual mode entry points for use in Node.js but not the browser:

{
  "exports": {
    "node": {
      "import": "./feature-node.mjs",
      "require": "./feature-node.cjs"
    },
    "default": "./feature.mjs"
  }
} 

条件继续按顺序与扁平条件匹配。如果嵌套条件没有任何映射,它将继续检查父条件的剩余条件。通过这种方式,嵌套条件的行为类似于嵌套的 JavaScript if 语句。

¥Conditions continue to be matched in order as with flat conditions. If a nested condition does not have any mapping it will continue checking the remaining conditions of the parent condition. In this way nested conditions behave analogously to nested JavaScript if statements.

解析用户条件#

¥Resolving user conditions

运行 Node.js 时,可以使用 --conditions 标志添加自定义用户条件:

¥When running Node.js, custom user conditions can be added with the --conditions flag:

node --conditions=development index.js 

然后将解析包导入和导出中的 "development" 条件,同时根据需要解析现有的 "node""node-addons""default""import""require" 条件。

¥which would then resolve the "development" condition in package imports and exports, while resolving the existing "node", "node-addons", "default", "import", and "require" conditions as appropriate.

可以使用重复标志设置任意数量的自定义条件。

¥Any number of custom conditions can be set with repeat flags.

典型条件应仅包含字母数字字符,如有必要,使用 ":"、"*" 或 "=" 作为分隔符。任何其他操作都可能在节点之外遇到兼容性问题。

¥Typical conditions should only contain alphanumerical characters, using ":", "-", or "=" as separators if necessary. Anything else may run into compability issues outside of node.

在节点中,条件几乎没有限制,但具体包括:

¥In node, conditions have very few restrictions, but specifically these include:

  1. 它们必须至少包含一个字符。

    ¥They must contain at least one character.

  2. 它们不能以 "." 开头,因为它们可能出现在也允许相对路径的地方。

    ¥They cannot start with "." since they may appear in places that also allow relative paths.

  3. 它们不能包含 ",",因为它们可能会被某些 CLI 工具解析为逗号分隔的列表。

    ¥They cannot contain "," since they may be parsed as a comma-separated list by some CLI tools.

  4. 它们不能是像 "10" 这样的整数属性键,因为这会对 JS 对象的属性键排序产生意外影响。

    ¥They cannot be integer property keys like "10" since that can have unexpected effects on property key ordering for JS objects.

社区条件定义#

¥Community Conditions Definitions

默认情况下,除 "import""require""node""module-sync""node-addons""default" 条件 在 Node.js 核心中实现 之外的条件字符串将被忽略。

¥Condition strings other than the "import", "require", "node", "module-sync", "node-addons" and "default" conditions implemented in Node.js core are ignored by default.

其他平台可能会实现其他条件,用户条件可以通过 --conditions / -C 标志 在 Node.js 中启用。

¥Other platforms may implement other conditions and user conditions can be enabled in Node.js via the --conditions / -C flag.

由于自定义的包条件需要明确定义以确保正确使用,因此下面提供了常见的已知包条件及其严格定义的列表,以协助生态系统协调。

¥Since custom package conditions require clear definitions to ensure correct usage, a list of common known package conditions and their strict definitions is provided below to assist with ecosystem coordination.

  • "types" - 类型系统可以使用它来解析给定导出的类型文件。应始终首先包括此条件。

    ¥"types" - can be used by typing systems to resolve the typing file for the given export. This condition should always be included first.

  • "browser" - 任何网络浏览器环境。

    ¥"browser" - any web browser environment.

  • "development" - 可用于定义仅开发环境入口点,例如在开发模式下运行时提供额外的调试上下文,例如更好的错误消息。必须始终与 "production" 互斥。

    ¥"development" - can be used to define a development-only environment entry point, for example to provide additional debugging context such as better error messages when running in a development mode. Must always be mutually exclusive with "production".

  • "production" - 可用于定义生产环境入口点。必须始终与 "development" 互斥。

    ¥"production" - can be used to define a production environment entry point. Must always be mutually exclusive with "development".

对于其他运行时,特定于平台的条件键定义由 运行时键 提案规范中的 WinterCG 维护。

¥For other runtimes, platform-specific condition key definitions are maintained by the WinterCG in the Runtime Keys proposal specification.

通过向 本节的 Node.js 文档 创建拉取请求,可以将新的条件定义添加到此列表中。在此处列出新条件定义的要求是:

¥New conditions definitions may be added to this list by creating a pull request to the Node.js documentation for this section. The requirements for listing a new condition definition here are that:

  • 对于所有实现者来说,定义应该是清晰明确的。

    ¥The definition should be clear and unambiguous for all implementers.

  • 为什么需要条件的用例应该清楚地证明。

    ¥The use case for why the condition is needed should be clearly justified.

  • 应该存在足够的现有实现用法。

    ¥There should exist sufficient existing implementation usage.

  • 条件名称不应与另一个条件定义或广泛使用的条件冲突。

    ¥The condition name should not conflict with another condition definition or condition in wide usage.

  • 条件定义的列表应该为生态系统提供协调效益,否则这是不可能的。例如,对于特定于公司或特定于应用的条件,情况不一定如此。

    ¥The listing of the condition definition should provide a coordination benefit to the ecosystem that wouldn't otherwise be possible. For example, this would not necessarily be the case for company-specific or application-specific conditions.

  • 该条件应该是 Node.js 用户期望它出现在 Node.js 核心文档中。"types" 条件就是一个很好的例子:它并不真正属于 运行时键 提案,但很适合 Node.js 文档。

    ¥The condition should be such that a Node.js user would expect it to be in Node.js core documentation. The "types" condition is a good example: It doesn't really belong in the Runtime Keys proposal but is a good fit here in the Node.js docs.

上述定义可能会在适当的时候移到专门的条件仓库中。

¥The above definitions may be moved to a dedicated conditions registry in due course.

使用名称自引用包#

¥Self-referencing a package using its name

在一个包中,包的 package.json "exports" 字段中定义的值可以通过包的名称引用。例如,假设 package.json 是:

¥Within a package, the values defined in the package's package.json "exports" field can be referenced via the package's name. For example, assuming the package.json is:

// package.json
{
  "name": "a-package",
  "exports": {
    ".": "./index.mjs",
    "./foo.js": "./foo.js"
  }
} 

然后该包中的任何模块都可以引用包本身的导出:

¥Then any module in that package can reference an export in the package itself:

// ./a-module.mjs
import { something } from 'a-package'; // Imports "something" from ./index.mjs. 

自引用仅在 package.json 具有 "exports" 时可用,并且只允许导入 "exports"(在 package.json 中)允许的内容。所以下面的代码,给定前面的包,会产生运行时错误:

¥Self-referencing is available only if package.json has "exports", and will allow importing only what that "exports" (in the package.json) allows. So the code below, given the previous package, will generate a runtime error:

// ./another-module.mjs

// Imports "another" from ./m.mjs. Fails because
// the "package.json" "exports" field
// does not provide an export named "./m.mjs".
import { another } from 'a-package/m.mjs'; 

在 ES 模块和 CommonJS 模块中使用 require 时也可以使用自引用。例如,这段代码也可以工作:

¥Self-referencing is also available when using require, both in an ES module, and in a CommonJS one. For example, this code will also work:

// ./a-module.js
const { something } = require('a-package/foo.js'); // Loads from ./foo.js. 

最后,自引用也适用于范围包。例如,这段代码也可以工作:

¥Finally, self-referencing also works with scoped packages. For example, this code will also work:

// package.json
{
  "name": "@my/package",
  "exports": "./index.js"
} 
// ./index.js
module.exports = 42; 
// ./other.js
console.log(require('@my/package')); 
$ node other.js
42 

双 CommonJS/ES 模块包#

¥Dual CommonJS/ES module packages

详见 包示例存储库

¥See the package examples repository for details.

Node.js package.json 字段定义#

¥Node.js package.json field definitions

本节描述了 Node.js 运行时使用的字段。其他工具(例如 npm)使用其他字段,这些字段被 Node.js 忽略且未在此处记录。

¥This section describes the fields used by the Node.js runtime. Other tools (such as npm) use additional fields which are ignored by Node.js and not documented here.

package.json 文件中的以下字段在 Node.js 中使用:

¥The following fields in package.json files are used in Node.js:

  • "name" - 在包中使用命名导入时相关。也被包管理器用作包的名称。

    ¥"name" - Relevant when using named imports within a package. Also used by package managers as the name of the package.

  • "main" - 加载包时的默认模块,如果没有指定 exports,在引入 exports 之前的 Node.js 版本中。

    ¥"main" - The default module when loading the package, if exports is not specified, and in versions of Node.js prior to the introduction of exports.

  • "packageManager" - 包管理器在贡献包时推荐。由 Corepack 垫片利用。

    ¥"packageManager" - The package manager recommended when contributing to the package. Leveraged by the Corepack shims.

  • "type" - 决定是否将 .js 文件加载为 CommonJS 或 ES 模块的包类型。

    ¥"type" - The package type determining whether to load .js files as CommonJS or ES modules.

  • "exports" - 包导出和条件导出。当存在时,限制可以从包中加载哪些子模块。

    ¥"exports" - Package exports and conditional exports. When present, limits which submodules can be loaded from within the package.

  • "imports" - 包导入,供包本身内的模块使用。

    ¥"imports" - Package imports, for use by modules within the package itself.

"name"#

{
  "name": "package-name"
} 

"name" 字段定义了你的包名。发布到 npm 注册表需要满足 特定要求 的名称。

¥The "name" field defines your package's name. Publishing to the npm registry requires a name that satisfies certain requirements.

除了 "exports" 字段之外,"name" 字段还可以用于 self-reference 使用其名称的包。

¥The "name" field can be used in addition to the "exports" field to self-reference a package using its name.

"main"#

{
  "main": "./index.js"
} 

当通过 node_modules 查找按名称导入时,则 "main" 字段定义了包的入口点。其值为路径。

¥The "main" field defines the entry point of a package when imported by name via a node_modules lookup. Its value is a path.

当包具有 "exports" 字段时,则在按名称导入包时,这将优先于 "main" 字段。

¥When a package has an "exports" field, this will take precedence over the "main" field when importing the package by name.

它还定义了 包目录是通过 require() 加载的 时使用的脚本。

¥It also defines the script that is used when the package directory is loaded via require().

// This resolves to ./path/to/directory/index.js.
require('./path/to/directory'); 

"packageManager"#

稳定性: 1 - 实验性的

¥Stability: 1 - Experimental

{
  "packageManager": "<package manager name>@<version>"
} 

"packageManager" 字段定义了在处理当前项目时预期使用的包管理器。它可以设置为任何 支持的包管理器,并且将确保你的团队使用完全相同的包管理器版本,而无需安装除 Node.js 之外的任何其他东西。

¥The "packageManager" field defines which package manager is expected to be used when working on the current project. It can be set to any of the supported package managers, and will ensure that your teams use the exact same package manager versions without having to install anything else other than Node.js.

该字段目前处于试验阶段,需要选择加入;检查 Corepack 页面以了解有关该过程的详细信息。

¥This field is currently experimental and needs to be opted-in; check the Corepack page for details about the procedure.

"type"#

"type" 字段定义了 Node.js 用于所有 .js 文件的模块格式,这些 .js 文件将该 package.json 文件作为其最近的父文件。

¥The "type" field defines the module format that Node.js uses for all .js files that have that package.json file as their nearest parent.

当最近的父 package.json 文件包含值为 "module" 的顶层字段 "type" 时,以 .js 结尾的文件将作为 ES 模块加载。

¥Files ending with .js are loaded as ES modules when the nearest parent package.json file contains a top-level field "type" with a value of "module".

最近的父 package.json 定义为在当前文件夹、该文件夹的父文件夹等中搜索时找到的第一个 package.json,依此类推,直到到达 node_modules 文件夹或卷根。

¥The nearest parent package.json is defined as the first package.json found when searching in the current folder, that folder's parent, and so on up until a node_modules folder or the volume root is reached.

// package.json
{
  "type": "module"
} 
# In same folder as preceding package.json
node my-app.js # Runs as ES module 

如果最近的父 package.json 缺少 "type" 字段,或包含 "type": "commonjs",则 .js 文件将被视为 CommonJS。如果到达卷根目录但未找到 package.json,则 .js 文件将被视为 CommonJS

¥If the nearest parent package.json lacks a "type" field, or contains "type": "commonjs", .js files are treated as CommonJS. If the volume root is reached and no package.json is found, .js files are treated as CommonJS.

如果最近的父 package.json 包含 "type": "module",则 .js 文件的 import 语句被视为 ES 模块。

¥import statements of .js files are treated as ES modules if the nearest parent package.json contains "type": "module".

// my-app.js, part of the same example as above
import './startup.js'; // Loaded as ES module because of package.json 

无论 "type" 字段的值如何,.mjs 文件始终被视为 ES 模块,而 .cjs 文件始终被视为 CommonJS。

¥Regardless of the value of the "type" field, .mjs files are always treated as ES modules and .cjs files are always treated as CommonJS.

"exports"#

{
  "exports": "./index.js"
} 

"exports" 字段允许在通过 node_modules 查找或 self-reference 加载到其自己的名称的名称导入时定义包的 入口点。它在 Node.js 12+ 中被支持作为 "main" 的替代品,它可以支持定义 子路径导出条件导出,同时封装内部未导出的模块。

¥The "exports" field allows defining the entry points of a package when imported by name loaded either via a node_modules lookup or a self-reference to its own name. It is supported in Node.js 12+ as an alternative to the "main" that can support defining subpath exports and conditional exports while encapsulating internal unexported modules.

条件导出 也可以在 "exports" 中使用,为每个环境定义不同的包入口点,包括包是通过 require 还是通过 import 引用。

¥Conditional Exports can also be used within "exports" to define different package entry points per environment, including whether the package is referenced via require or via import.

"exports" 中定义的所有路径必须是以 ./ 开头的相对文件 URL。

¥All paths defined in the "exports" must be relative file URLs starting with ./.

"imports"#

// package.json
{
  "imports": {
    "#dep": {
      "node": "dep-node-native",
      "default": "./dep-polyfill.js"
    }
  },
  "dependencies": {
    "dep-node-native": "^1.0.0"
  }
} 

导入字段中的条目必须是以 # 开头的字符串。

¥Entries in the imports field must be strings starting with #.

包导入允许映射到外部包。

¥Package imports permit mapping to external packages.

该字段为当前包定义 子路径导入

¥This field defines subpath imports for the current package.

Node.js 中文网 - 粤ICP备13048890号