- assert 断言
- async_hooks 异步钩子
- async_hooks/context 异步上下文
- buffer 缓冲区
- C++插件
- C/C++插件(使用 Node-API)
- C++嵌入器
- child_process 子进程
- cluster 集群
- CLI 命令行
- console 控制台
- Corepack 核心包
- crypto 加密
- crypto/webcrypto 网络加密
- debugger 调试器
- deprecation 弃用
- dgram 数据报
- diagnostics_channel 诊断通道
- dns 域名服务器
- domain 域
- Error 错误
- events 事件触发器
- fs 文件系统
- global 全局变量
- http 超文本传输协议
- http2 超文本传输协议 2.0
- https 安全超文本传输协议
- inspector 检查器
- Intl 国际化
- module 模块
- module/cjs CommonJS 模块
- module/esm ECMAScript 模块
- module/package 包模块
- net 网络
- os 操作系统
- path 路径
- perf_hooks 性能钩子
- permission 权限
- process 进程
- punycode 域名代码
- querystring 查询字符串
- readline 逐行读取
- repl 交互式解释器
- report 诊断报告
- stream 流
- stream/web 网络流
- string_decoder 字符串解码器
- test 测试
- timers 定时器
- tls 安全传输层
- trace_events 跟踪事件
- tty 终端
- url 网址
- util 实用工具
- v8 引擎
- vm 虚拟机
- wasi 网络汇编系统接口
- worker_threads 工作线程
- zlib 压缩
Node.js v16.20.0 文档
- Node.js v16.20.0
- 目录
-
导航
- assert 断言
- async_hooks 异步钩子
- async_hooks/context 异步上下文
- buffer 缓冲区
- C++插件
- C/C++插件(使用 Node-API)
- C++嵌入器
- child_process 子进程
- cluster 集群
- CLI 命令行
- console 控制台
- Corepack 核心包
- crypto 加密
- crypto/webcrypto 网络加密
- debugger 调试器
- deprecation 弃用
- dgram 数据报
- diagnostics_channel 诊断通道
- dns 域名服务器
- domain 域
- Error 错误
- events 事件触发器
- fs 文件系统
- global 全局变量
- http 超文本传输协议
- http2 超文本传输协议 2.0
- https 安全超文本传输协议
- inspector 检查器
- Intl 国际化
- module 模块
- module/cjs CommonJS 模块
- module/esm ECMAScript 模块
- module/package 包模块
- net 网络
- os 操作系统
- path 路径
- perf_hooks 性能钩子
- permission 权限
- process 进程
- punycode 域名代码
- querystring 查询字符串
- readline 逐行读取
- repl 交互式解释器
- report 诊断报告
- stream 流
- stream/web 网络流
- string_decoder 字符串解码器
- test 测试
- timers 定时器
- tls 安全传输层
- trace_events 跟踪事件
- tty 终端
- url 网址
- util 实用工具
- v8 引擎
- vm 虚拟机
- wasi 网络汇编系统接口
- worker_threads 工作线程
- zlib 压缩
- 其他版本
模块:包#
¥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
当传递给 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 parentpackage.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 tonode
viaSTDIN
, with the flag--input-type=module
.
Node.js 会将所有其他形式的输入视为 CommonJS,例如最近的父 package.json
文件不包含顶层 "type"
字段的 .js
文件,或没有标志 --input-type
的字符串输入。此行为是为了保持向后兼容性。但是,现在 Node.js 同时支持 CommonJS 和 ES 模块,最好尽可能明确。当作为初始输入传给 node
、或者当被 import
语句或 import()
表达式或 require()
表达式引用时,Node.js 会将以下视为 CommonJS:
¥Node.js will treat as CommonJS all other forms of input, such as .js
files
where the nearest parent package.json
file contains no top-level "type"
field, or string input without the flag --input-type
. This behavior is to
preserve backward compatibility. However, now that Node.js supports both
CommonJS and ES modules, it is best to be explicit whenever possible. Node.js
will treat the following as CommonJS when passed to node
as the initial input,
or when referenced by import
statements, import()
expressions, or
require()
expressions:
-
扩展名为
.cjs
的文件。¥Files with a
.cjs
extension. -
当最近的父
package.json
文件包含值为"commonjs"
的顶层字段"type"
时,则扩展名为.js
的文件。¥Files with a
.js
extension when the nearest parentpackage.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 tonode
viaSTDIN
, with the flag--input-type=commonjs
.
包作者应该包括 "type"
字段,即使在所有源都是 CommonJS 的包中也是如此。如果 Node.js 的默认类型发生变化,显式说明包的 type
将使包面向未来,它还将使构建工具和加载器更容易确定应如何解释包中的文件。
¥Package authors should include the "type"
field, 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 withprocess.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 andimport()
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
¥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。例如,之前导出 main
、lib
、feature
和 package.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"
- 当包通过import
或import()
加载时匹配,或者通过 ECMAScript 模块加载器通过任何顶层导入或解析操作加载。无论目标文件的模块格式如何,都适用。始终与"require"
互斥。¥
"import"
- matches when the package is loaded viaimport
orimport()
, 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 viarequire()
. The referenced file should be loadable withrequire()
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 asrequire()
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. -
"deno"
- 表示 Deno 平台的变体。¥
"deno"
- indicates a variation for the Deno platform. -
"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"
.
通过向 本节的 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.
上述定义可能会在适当的时候移到专门的条件仓库中。
¥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
子路径文件夹映射#
¥Subpath folder mappings
¥Stability: 0 - Deprecated: Use subpath patterns instead.
在支持子路径模式之前,尾随的 "/"
后缀用于支持文件夹映射:
¥Before subpath patterns were supported, a trailing "/"
suffix was used to
support folder mappings:
{
"exports": {
"./features/": "./features/"
}
}
此功能将在未来版本中删除。
¥This feature will be removed in a future release.
相反,使用直接 子路径模式:
¥Instead, use direct subpath patterns:
{
"exports": {
"./features/*": "./features/*.js"
}
}
模式优于文件夹导出的好处是,消费者始终可以导入包,而无需子路径文件扩展名。
¥The benefit of patterns over folder exports is that packages can always be imported by consumers without subpath file extensions being necessary.
双 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')
创建的 pkgInstance
与 import 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
)在另一个上不存在。这与 import
和 require
语句分别在全 CommonJS 或全 ES 模块环境中的工作方式不同,因此令用户感到惊讶。它也不同于用户在通过 Babel 或 esm
等工具使用转译时所熟悉的行为。
¥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:
-
该软件包可通过
require
和import
使用。¥The package is usable via both
require
andimport
. -
该包在当前 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.
-
包主入口点,例如
'pkg'
可以被require
用来解析 CommonJS 文件,也可以被import
用来解析 ES 模块文件。(对于导出的路径也是如此,例如'pkg/feature'
。)¥The package main entry point, e.g.
'pkg'
can be used by bothrequire
to resolve to a CommonJS file and byimport
to resolve to an ES module file. (And likewise for exported paths, e.g.'pkg/feature'
.) -
该包提供命名导出,例如
import { name } from 'pkg'
而不是import pkg from 'pkg'; pkg.name
。¥The package provides named exports, e.g.
import { name } from 'pkg'
rather thanimport pkg from 'pkg'; pkg.name
. -
该包可能在其他 ES 模块环境中可用,例如浏览器。
¥The package is potentially usable in other ES module environments such as browsers.
-
避免或最小化上一节中描述的危害。
¥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'
中的 name
与 const { 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 name
s 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 autilities-plus
package adds a few more functions toutilities
. Because the wrapper exports underlying CommonJS files, it doesn't matter ifutilities-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
require
s 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:
-
如果可能,在实例化对象中包含所有状态。例如,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. -
在包的 CommonJS 和 ES 模块版本之间共享的一个或多个 CommonJS 文件中隔离状态。比如 CommonJS 和 ES 模块入口点分别是
index.cjs
和index.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
andindex.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 };
即使在应用中通过
require
和import
使用pkg
(例如,在应用代码中通过import
并通过依赖通过require
),pkg
的每个引用都将包含相同的状态;并且从任一模块系统修改该状态将适用于两者。¥Even if
pkg
is used via bothrequire
andimport
in an application (for example, viaimport
in application code and viarequire
by a dependency) each reference ofpkg
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"
#
¥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 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 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"
#
-
类型:<Object> | <string> | <string[]>
¥Type: <Object> | <string> | <string[]>
{
"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.