Node.js v20.11.1 文档


模块:包#

¥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 that contains syntax that only parses successfully as ES modules, such as import or export statements or import.meta, when the code has 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 cause a file to be treated as an ES module.

当传递给 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.

模块加载器#

¥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.

  • 它不能用于加载 ECMAScript 模块(尽管 从 CommonJS 模块加载 ECMASCript 模块 是可能的)。当用于加载不是 ECMAScript 模块的 JavaScript 文本文件时,则它将作为 CommonJS 模块加载。

    ¥It cannot be used to load ECMAScript modules (although it is possible to load ECMASCript modules from CommonJS modules). When used to load a JavaScript text file that is not an ECMAScript module, it loads it as a CommonJS module.

有 ECMAScript 模块加载器:

¥There is the ECMAScript module loader:

  • 它是异步的。

    ¥It is asynchronous.

  • 负责处理 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 assertion 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 模块,因为 require() 不支持它们。始终与 "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, and native addons but not ES modules as require() doesn't support them. Always mutually exclusive with "import".

  • "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.

社区条件定义#

¥Community Conditions Definitions

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

¥Condition strings other than the "import", "require", "node", "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

在 Node.js 中引入对 ES 模块的支持之前,包作者的一种常见模式是在他们的包中包含 CommonJS 和 ES 模块 JavaScript 源代码,其中 package.json "main" 指定了 CommonJS 入口点,而 package.json "module" 指定了 ES 模块入口点。这使 Node.js 能够运行 CommonJS 入口点,而构建工具(例如打包器)使用 ES 模块入口点,因为 Node.js 忽略(并且仍然忽略)顶层 "module" 字段。

¥Prior to the introduction of support for ES modules in Node.js, it was a common pattern for package authors to include both CommonJS and ES module JavaScript sources in their package, with package.json "main" specifying the CommonJS entry point and package.json "module" specifying the ES module entry point. This enabled Node.js to run the CommonJS entry point while build tools such as bundlers used the ES module entry point, since Node.js ignored (and still ignores) the top-level "module" field.

Node.js 现在可以运行 ES 模块入口点,并且一个包可以同时包含 CommonJS 和 ES 模块入口点(通过单独的说明符,例如 'pkg''pkg/es-module',或者通过 条件导出 在同一说明符中)。与 "module" 仅由打包程序使用的场景不同,或者在 Node.js 评估之前将 ES 模块文件动态转换为 CommonJS,ES 模块入口点引用的文件被评估为 ES 模块。

¥Node.js can now run ES module entry points, and a package can contain both CommonJS and ES module entry points (either via separate specifiers such as 'pkg' and 'pkg/es-module', or both at the same specifier via Conditional exports). Unlike in the scenario where "module" is only used by bundlers, or ES module files are transpiled into CommonJS on the fly before evaluation by Node.js, the files referenced by the ES module entry point are evaluated as ES modules.

双封装危害#

¥Dual package hazard

当应用使用提供 CommonJS 和 ES 模块源的包时,如果包的两个版本都被加载,则存在某些错误的风险。此潜力来自于 const pkgInstance = require('pkg') 创建的 pkgInstanceimport pkgInstance from 'pkg' 创建的 pkgInstance(或像 'pkg/module' 这样的替代主路径)不同的事实。这就是“双包危险”,即同一包的两个版本可以在同一运行时环境中加载。虽然应用或包不太可能有意直接加载两个版本,但应用加载一个版本而应用的依赖加载另一个版本是很常见的。这种危险可能发生,因为 Node.js 支持混合 CommonJS 和 ES 模块,并可能导致意外行为。

¥When an application is using a package that provides both CommonJS and ES module sources, there is a risk of certain bugs if both versions of the package get loaded. This potential comes from the fact that the pkgInstance created by const pkgInstance = require('pkg') is not the same as the pkgInstance created by import pkgInstance from 'pkg' (or an alternative main path like 'pkg/module'). This is the “dual package hazard,” where two versions of the same package can be loaded within the same runtime environment. While it is unlikely that an application or package would intentionally load both versions directly, it is common for an application to load one version while a dependency of the application loads the other version. This hazard can happen because Node.js supports intermixing CommonJS and ES modules, and can lead to unexpected behavior.

如果包主导出是一个构造函数,两个版本创建的实例的 instanceof 比较返回 false,如果导出是一个对象,添加到一个的属性(如 pkgInstance.foo = 3)在另一个上不存在。这与 importrequire 语句分别在全 CommonJS 或全 ES 模块环境中的工作方式不同,因此令用户感到惊讶。它也不同于用户在通过 Babelesm 等工具使用转译时所熟悉的行为。

¥If the package main export is a constructor, an instanceof comparison of instances created by the two versions returns false, and if the export is an object, properties added to one (like pkgInstance.foo = 3) are not present on the other. This differs from how import and require statements work in all-CommonJS or all-ES module environments, respectively, and therefore is surprising to users. It also differs from the behavior users are familiar with when using transpilation via tools like Babel or esm.

在避免或最小化危险的同时编写双重包#

¥Writing dual packages while avoiding or minimizing hazards

首先,当一个包同时包含 CommonJS 和 ES 模块源并且这两个源都通过单独的主入口点或导出路径提供以在 Node.js 中使用时,就会发生上一节中描述的危险。一个包可能被写成任何版本的 Node.js 只接收 CommonJS 源,并且包可能包含的任何单独的 ES 模块源仅用于其他环境,例如浏览器。这样的包可以被任何版本的 Node.js 使用,因为 import 可以引用 CommonJS 文件;但它不会提供使用 ES 模块语法的任何优势。

¥First, the hazard described in the previous section occurs when a package contains both CommonJS and ES module sources and both sources are provided for use in Node.js, either via separate main entry points or exported paths. A package might instead be written where any version of Node.js receives only CommonJS sources, and any separate ES module sources the package might contain are intended only for other environments such as browsers. Such a package would be usable by any version of Node.js, since import can refer to CommonJS files; but it would not provide any of the advantages of using ES module syntax.

一个包也可能在 重大改变 版本冲击中从 CommonJS 切换到 ES 模块语法。这有一个缺点,即最新版本的包只能在支持 ES 模块的 Node.js 版本中使用。

¥A package might also switch from CommonJS to ES module syntax in a breaking change version bump. This has the disadvantage that the newest version of the package would only be usable in ES module-supporting versions of Node.js.

每种模式都有权衡,但有两种广泛的方法可以满足以下条件:

¥Every pattern has tradeoffs, but there are two broad approaches that satisfy the following conditions:

  1. 该软件包可通过 requireimport 使用。

    ¥The package is usable via both require and import.

  2. 该包在当前 Node.js 和不支持 ES 模块的旧版本 Node.js 中都可用。

    ¥The package is usable in both current Node.js and older versions of Node.js that lack support for ES modules.

  3. 包主入口点,例如 'pkg' 可以被 require 用来解析 CommonJS 文件,也可以被 import 用来解析 ES 模块文件。(对于导出的路径也是如此,例如 'pkg/feature'。)

    ¥The package main entry point, e.g. 'pkg' can be used by both require to resolve to a CommonJS file and by import to resolve to an ES module file. (And likewise for exported paths, e.g. 'pkg/feature'.)

  4. 该包提供命名导出,例如 import { name } from 'pkg' 而不是 import pkg from 'pkg'; pkg.name

    ¥The package provides named exports, e.g. import { name } from 'pkg' rather than import pkg from 'pkg'; pkg.name.

  5. 该包可能在其他 ES 模块环境中可用,例如浏览器。

    ¥The package is potentially usable in other ES module environments such as browsers.

  6. 避免或最小化上一节中描述的危害。

    ¥The hazards described in the previous section are avoided or minimized.

方法 #1:使用 ES 模块封装器#

¥Approach #1: Use an ES module wrapper

在 CommonJS 中编写包或将 ES 模块源代码转换为 CommonJS,并创建定义命名导出的 ES 模块封装文件。使用 条件导出,ES 模块封装器用于 import,CommonJS 入口点用于 require

¥Write the package in CommonJS or transpile ES module sources into CommonJS, and create an ES module wrapper file that defines the named exports. Using Conditional exports, the ES module wrapper is used for import and the CommonJS entry point for require.

// ./node_modules/pkg/package.json
{
  "type": "module",
  "exports": {
    "import": "./wrapper.mjs",
    "require": "./index.cjs"
  }
} 

前面的示例使用显式扩展 .mjs.cjs。如果你的文件使用 .js 扩展名,"type": "module" 会导致这些文件被视为 ES 模块,就像 "type": "commonjs" 会导致它们被视为 CommonJS。参见 启用

¥The preceding example uses explicit extensions .mjs and .cjs. If your files use the .js extension, "type": "module" will cause such files to be treated as ES modules, just as "type": "commonjs" would cause them to be treated as CommonJS. See Enabling.

// ./node_modules/pkg/index.cjs
exports.name = 'value'; 
// ./node_modules/pkg/wrapper.mjs
import cjsModule from './index.cjs';
export const name = cjsModule.name; 

在这个例子中,import { name } from 'pkg' 中的 nameconst { name } = require('pkg') 中的 name 是相同的单例。因此,=== 在比较两个 name 时返回 true,避免了不同的说明符危险。

¥In this example, the name from import { name } from 'pkg' is the same singleton as the name from const { name } = require('pkg'). Therefore === returns true when comparing the two names and the divergent specifier hazard is avoided.

如果模块不是简单的命名导出列表,而是包含独特的函数或对象导出,如 module.exports = function () { ... },或者如果需要封装器支持 import pkg from 'pkg' 模式,则封装器将被编写为可选地导出默认值以及任何命名的导出:

¥If the module is not simply a list of named exports, but rather contains a unique function or object export like module.exports = function () { ... }, or if support in the wrapper for the import pkg from 'pkg' pattern is desired, then the wrapper would instead be written to export the default optionally along with any named exports as well:

import cjsModule from './index.cjs';
export const name = cjsModule.name;
export default cjsModule; 

此方法适用于以下任何用例:

¥This approach is appropriate for any of the following use cases:

  • 该包目前是用 CommonJS 编写的,作者不希望将其重构为 ES 模块语法,而是希望为 ES 模块使用者提供命名导出。

    ¥The package is currently written in CommonJS and the author would prefer not to refactor it into ES module syntax, but wishes to provide named exports for ES module consumers.

  • 该包还有其他依赖它的包,终端用户可能会同时安装这个包和那些其他包。比如 utilities 包直接在应用中使用,utilities-plus 包给 utilities 增加了一些功能。因为封装器导出底层 CommonJS 文件,所以 utilities-plus 是用 CommonJS 还是 ES 模块语法编写的并不重要;它会以任何方式工作。

    ¥The package has other packages that depend on it, and the end user might install both this package and those other packages. For example a utilities package is used directly in an application, and a utilities-plus package adds a few more functions to utilities. Because the wrapper exports underlying CommonJS files, it doesn't matter if utilities-plus is written in CommonJS or ES module syntax; it will work either way.

  • 包存储内部状态,包作者宁愿不重构包以隔离其状态管理。请参阅下一章节。

    ¥The package stores internal state, and the package author would prefer not to refactor the package to isolate its state management. See the next section.

此方法的变体不需要消费者有条件导出,可以添加一个导出,例如 "./module",指向包的全 ES 模块语法版本。确定 CommonJS 版本不会在应用的任何地方加载的用户可以通过 import 'pkg/module' 使用它,例如依赖;或者如果可以加载 CommonJS 版本但不影响 ES 模块版本(例如,因为包是无状态的):

¥A variant of this approach not requiring conditional exports for consumers could be to add an export, e.g. "./module", to point to an all-ES module-syntax version of the package. This could be used via import 'pkg/module' by users who are certain that the CommonJS version will not be loaded anywhere in the application, such as by dependencies; or if the CommonJS version can be loaded but doesn't affect the ES module version (for example, because the package is stateless):

// ./node_modules/pkg/package.json
{
  "type": "module",
  "exports": {
    ".": "./index.cjs",
    "./module": "./wrapper.mjs"
  }
} 

方法 #2:隔离状态#

¥Approach #2: Isolate state

package.json 文件可以直接定义单独的 CommonJS 和 ES 模块入口点:

¥A package.json file can define the separate CommonJS and ES module entry points directly:

// ./node_modules/pkg/package.json
{
  "type": "module",
  "exports": {
    "import": "./index.mjs",
    "require": "./index.cjs"
  }
} 

如果包的 CommonJS 和 ES 模块版本是等效的,则可以这样做,例如,因为一个是另一个的转译输出;并且包的状态管理被仔细隔离(或者包是无状态的)。

¥This can be done if both the CommonJS and ES module versions of the package are equivalent, for example because one is the transpiled output of the other; and the package's management of state is carefully isolated (or the package is stateless).

状态是一个问题的原因是因为包的 CommonJS 和 ES 模块版本都可能在应用中使用;例如,用户的应用代码可以 import ES 模块版本,而依赖 require CommonJS 版本。如果发生这种情况,包的两个副本将被加载到内存中,因此将出现两个不同的状态。这可能会导致难以解决的错误。

¥The reason that state is an issue is because both the CommonJS and ES module versions of the package might get used within an application; for example, the user's application code could import the ES module version while a dependency requires the CommonJS version. If that were to occur, two copies of the package would be loaded in memory and therefore two separate states would be present. This would likely cause hard-to-troubleshoot bugs.

除了编写无状态包(例如,如果 JavaScript 的 Math 是一个包,它将是无状态的,因为它的所有方法都是静态的),还有一些方法可以隔离状态,以便在可能加载的 CommonJS 和 ES 模块之间共享它包的实例:

¥Aside from writing a stateless package (if JavaScript's Math were a package, for example, it would be stateless as all of its methods are static), there are some ways to isolate state so that it's shared between the potentially loaded CommonJS and ES module instances of the package:

  1. 如果可能,在实例化对象中包含所有状态。例如,JavaScript 的 Date 需要实例化以包含状态;如果它是一个包,它会像这样使用:

    ¥If possible, contain all state within an instantiated object. JavaScript's Date, for example, needs to be instantiated to contain state; if it were a package, it would be used like this:

    import Date from 'date';
    const someDate = new Date();
    // someDate contains state; Date does not 

    new 关键字不是必需的;包的函数可以返回一个新对象,或修改一个传入的对象,以保持包外部的状态。

    ¥The new keyword isn't required; a package's function can return a new object, or modify a passed-in object, to keep the state external to the package.

  2. 在包的 CommonJS 和 ES 模块版本之间共享的一个或多个 CommonJS 文件中隔离状态。比如 CommonJS 和 ES 模块入口点分别是 index.cjsindex.mjs

    ¥Isolate the state in one or more CommonJS files that are shared between the CommonJS and ES module versions of the package. For example, if the CommonJS and ES module entry points are index.cjs and index.mjs, respectively:

    // ./node_modules/pkg/index.cjs
    const state = require('./state.cjs');
    module.exports.state = state; 
    // ./node_modules/pkg/index.mjs
    import state from './state.cjs';
    export {
      state,
    }; 

    即使在应用中通过 requireimport 使用 pkg(例如,在应用代码中通过 import 并通过依赖通过 require),pkg 的每个引用都将包含相同的状态;并且从任一模块系统修改该状态将适用于两者。

    ¥Even if pkg is used via both require and import in an application (for example, via import in application code and via require by a dependency) each reference of pkg will contain the same state; and modifying that state from either module system will apply to both.

任何附加到包单例的插件都需要分别附加到 CommonJS 和 ES 模块单例。

¥Any plugins that attach to the package's singleton would need to separately attach to both the CommonJS and ES module singletons.

此方法适用于以下任何用例:

¥This approach is appropriate for any of the following use cases:

  • 该包目前是用 ES 模块语法编写的,包作者希望在支持此类语法的任何地方使用该版本。

    ¥The package is currently written in ES module syntax and the package author wants that version to be used wherever such syntax is supported.

  • 包是无状态的,或者它的状态可以很容易地被隔离。

    ¥The package is stateless or its state can be isolated without too much difficulty.

  • 该包不太可能有其他依赖它的公共包,或者如果有,则该包是无状态的,或者具有不需要在依赖之间或与整个应用共享的状态。

    ¥The package is unlikely to have other public packages that depend on it, or if it does, the package is stateless or has state that need not be shared between dependencies or with the overall application.

即使处于隔离状态,在 CommonJS 和 ES 模块版本之间仍然存在可能执行额外代码的成本。

¥Even with isolated state, there is still the cost of possible extra code execution between the CommonJS and ES module versions of a package.

与之前的方法一样,这种方法的变体不需要消费者有条件的导出,可以添加一个导出,例如 "./module",指向包的全 ES 模块语法版本:

¥As with the previous approach, a variant of this approach not requiring conditional exports for consumers could be to add an export, e.g. "./module", to point to an all-ES module-syntax version of the package:

// ./node_modules/pkg/package.json
{
  "type": "module",
  "exports": {
    ".": "./index.cjs",
    "./module": "./index.mjs"
  }
} 

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.