模块:包
[历史]
版本 | 变更 |
---|---|
v14.13.0, v12.20.0 | 添加对 "exports" 模式的支持。 |
v14.6.0, v12.19.0 | 添加包 "imports" 字段。 |
v13.7.0, v12.17.0 | 取消条件导出的实验性标志。 |
v13.7.0, v12.16.0 | 删除 --experimental-conditional-exports 选项。在 12.16.0 中,条件导出仍然位于 --experimental-modules 之后。 |
v13.6.0, v12.16.0 | 取消使用其名称自引用的包的实验性标志。 |
v12.7.0 | 引入 "exports" package.json 字段作为经典 "main" 字段更强大的替代方案。 |
v12.0.0 | 通过 package.json "type" 字段添加对使用 .js 文件扩展名的 ES 模块的支持。 |
简介
包是一个由 package.json
文件描述的文件夹树。包由包含 package.json
文件的文件夹以及所有子文件夹组成,直到下一个包含另一个 package.json
文件的文件夹,或名为 node_modules
的文件夹。
此页面为编写 package.json
文件的包作者提供指导,并提供 Node.js 定义的 package.json
字段的参考。
模块系统确定
介绍
Node.js 会将以下内容视为 ES 模块,前提是将其作为初始输入传递给 node
,或者通过 import
语句或 import()
表达式引用:
- 扩展名为
.mjs
的文件。 - 扩展名为
.js
的文件,当最近的父package.json
文件包含一个值为"module"
的顶级"type"
字段时。 - 作为参数传递给
--eval
或通过STDIN
传递给node
的字符串,并带有--input-type=module
标志。 - 包含仅能成功解析为 ES 模块 的语法的代码,例如
import
或export
语句或import.meta
,并且没有明确标记其解释方式。明确的标记包括.mjs
或.cjs
扩展名、package.json
中"type"
字段的值为"module"
或"commonjs"
,或者--input-type
标志。动态import()
表达式在 CommonJS 或 ES 模块中都受支持,并且不会强制将文件视为 ES 模块。请参阅 语法检测。
Node.js 会将以下内容视为 CommonJS,前提是将其作为初始输入传递给 node
,或者通过 import
语句或 import()
表达式引用:
- 扩展名为
.cjs
的文件。 - 扩展名为
.js
的文件,当最近的父package.json
文件包含一个值为"commonjs"
的顶级字段"type"
时。 - 作为参数传递给
--eval
或--print
,或通过STDIN
传递给node
的字符串,并带有--input-type=commonjs
标志。 - 扩展名为
.js
的文件,且没有父package.json
文件,或者最近的父package.json
文件缺少type
字段,并且代码可以成功地作为 CommonJS 进行评估。换句话说,Node.js 首先尝试将此类“不明确”文件作为 CommonJS 运行,如果由于解析器发现 ES 模块语法导致 CommonJS 评估失败,则会重试将其评估为 ES 模块。
在“不明确”文件中编写 ES 模块语法会带来性能成本,因此建议作者尽可能明确。特别是,包作者应该始终在其 package.json
文件中包含 "type"
字段,即使在所有源都是 CommonJS 的包中也是如此。明确包的 type
将使包在 Node.js 的默认类型发生更改时具有向后兼容性,并且还会使构建工具和加载器更容易确定如何解释包中的文件。
语法检测
[历史]
版本 | 变更 |
---|---|
v22.7.0 | 语法检测默认启用。 |
v21.1.0, v20.10.0 | 在 v21.1.0, v20.10.0 版本中添加 |
Node.js 将检查不明确输入的源代码以确定其是否包含 ES 模块语法;如果检测到此类语法,则输入将被视为 ES 模块。
不明确输入定义为:
- 扩展名为
.js
或无扩展名的文件;并且没有控制package.json
文件或缺少type
字段的文件。 - 当未指定
--input-type
时,字符串输入(--eval
或STDIN
)。
ES 模块语法定义为当作为 CommonJS 评估时会抛出错误的语法。这包括以下内容:
import
语句(但不包括import()
表达式,这在 CommonJS 中是有效的)。export
语句。import.meta
引用。- 模块顶层的
await
。 - CommonJS 包装器变量(
require
、module
、exports
、__dirname
、__filename
)的词法重新声明。
模块加载器
Node.js 有两种解析说明符和加载模块的系统。
其一是 CommonJS 模块加载器:
- 它完全是同步的。
- 它负责处理
require()
调用。 - 它可以被猴子补丁。
- 它支持文件夹作为模块。
- 在解析说明符时,如果找不到完全匹配项,它将尝试添加扩展名(
.js
、.json
,最后是.node
),然后尝试解析文件夹作为模块。 - 它将
.json
视为 JSON 文本文件。 .node
文件被解释为使用process.dlopen()
加载的编译后的附加模块。- 它将所有缺少
.json
或.node
扩展名的文件视为 JavaScript 文本文件。 - 它只能用于从 CommonJS 模块加载 ECMAScript 模块,前提是模块图是同步的(不包含任何顶级
await
)。当用于加载不是 ECMAScript 模块的 JavaScript 文本文件时,该文件将作为 CommonJS 模块加载。
其二是 ECMAScript 模块加载器:
- 除非用于为
require()
加载模块,否则它是异步的。 - 它负责处理
import
语句和import()
表达式。 - 它不能被猴子补丁,可以使用加载器钩子进行自定义。
- 它不支持文件夹作为模块,必须完全指定目录索引(例如
'./startup/index.js'
)。 - 它不进行扩展名搜索。当说明符是相对或绝对文件 URL 时,必须提供文件扩展名。
- 它可以加载 JSON 模块,但是需要 import 类型属性。
- 它只接受
.js
、.mjs
和.cjs
扩展名作为 JavaScript 文本文件。 - 它可以用来加载 JavaScript CommonJS 模块。此类模块将通过
cjs-module-lexer
传递,以尝试识别具名导出,如果可以通过静态分析确定,则可以使用这些导出。导入的 CommonJS 模块的 URL 将转换为绝对路径,然后通过 CommonJS 模块加载器加载。
package.json
和文件扩展名
在一个包内,package.json
的 "type"
字段定义了 Node.js 如何解释 .js
文件。如果 package.json
文件没有 "type"
字段,则 .js
文件将被视为 CommonJS。
package.json
的 "type"
值为 "module"
会告诉 Node.js 将该包内的 .js
文件解释为使用 ES 模块 语法。
"type"
字段不仅适用于初始入口点 (node my-app.js
),也适用于 import
语句和 import()
表达式引用的文件。
// my-app.js,因为同一个文件夹中存在一个 "type": "module" 的 package.json 文件,所以被视为 ES 模块。
import './startup/init.js'
// 作为 ES 模块加载,因为 ./startup 不包含 package.json 文件,因此继承上一级目录的 "type" 值。
import 'commonjs-package'
// 作为 CommonJS 加载,因为 ./node_modules/commonjs-package/package.json
// 缺少 "type" 字段或包含 "type": "commonjs"。
import './node_modules/commonjs-package/index.js'
// 作为 CommonJS 加载,因为 ./node_modules/commonjs-package/package.json
// 缺少 "type" 字段或包含 "type": "commonjs"。
以 .mjs
结尾的文件始终作为 ES 模块 加载,而不管最近的父级 package.json
如何。
以 .cjs
结尾的文件始终作为 CommonJS 加载,而不管最近的父级 package.json
如何。
import './legacy-file.cjs'
// 作为 CommonJS 加载,因为 .cjs 始终作为 CommonJS 加载。
import 'commonjs-package/src/index.mjs'
// 作为 ES 模块加载,因为 .mjs 始终作为 ES 模块加载。
.mjs
和 .cjs
扩展名可以用于在同一个包中混合类型:
- 在
"type": "module"
包中,可以通过使用.cjs
扩展名命名文件来指示 Node.js 将特定文件解释为 CommonJS(因为在"module"
包中,.js
和.mjs
文件都被视为 ES 模块)。 - 在
"type": "commonjs"
包中,可以通过使用.mjs
扩展名命名文件来指示 Node.js 将特定文件解释为 ES 模块(因为在"commonjs"
包中,.js
和.cjs
文件都被视为 CommonJS)。
--input-type
标记
新增于:v12.0.0
当设置 --input-type=module
标记时,作为 --eval
(或 -e
)参数传入的字符串,或通过 STDIN
传递给 node
的字符串,将被视为 ES 模块。
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
,则这是默认行为。
确定包管理器
虽然所有 Node.js 项目在发布后都应该能够被所有包管理器安装,但它们的开发团队通常需要使用一个特定的包管理器。为了简化这个过程,Node.js 附带了一个名为 Corepack 的工具,旨在使所有包管理器在您的环境中透明地可用——前提是您已安装 Node.js。
默认情况下,Corepack 不会强制使用任何特定的包管理器,并将使用与每个 Node.js 版本关联的通用“上次已知良好”版本,但您可以通过设置项目 package.json
文件中的 "packageManager"
字段来改善此体验。
包入口点
在一个包的 package.json
文件中,有两个字段可以定义包的入口点: "main"
和 "exports"
。这两个字段都适用于 ES 模块和 CommonJS 模块入口点。
"main"
字段在所有版本的 Node.js 中都受支持,但其功能有限:它仅定义包的主入口点。
"exports"
为 "main"
提供了一种现代替代方案,允许定义多个入口点,支持不同环境之间的条件入口解析,并防止使用除"exports"
中定义的入口点之外的任何其他入口点。这种封装允许模块作者清晰地定义其包的公共接口。
对于针对当前受支持的 Node.js 版本的新包,推荐使用 "exports"
字段。对于支持 Node.js 10 及以下版本的包,需要使用 "main"
字段。如果同时定义了 "exports"
和 "main"
,则在受支持的 Node.js 版本中,"exports"
字段优先于 "main"
。
可以在 "exports"
中使用条件导出 为每个环境定义不同的包入口点,包括包是通过 require
还是通过 import
引用的。有关在一个包中同时支持 CommonJS 和 ES 模块的更多信息,请参阅双 CommonJS/ES 模块包部分。
现有的引入 "exports"
字段的包将阻止包的使用者使用任何未定义的入口点,包括 package.json
(例如 require('your-package/package.json')
)。这很可能会导致重大更改。
为了使引入 "exports"
不产生重大更改,请确保导出所有以前支持的入口点。最好明确指定入口点,以便明确定义包的公共 API。例如,以前导出 main
、lib
、feature
和 package.json
的项目可以使用以下 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"
}
}
或者,项目可以选择使用导出模式导出带有和不带有扩展名的子路径的整个文件夹:
{
"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"
}
}
通过以上方式提供对任何次要包版本的向后兼容性,然后包的未来主要更改可以正确地将导出限制为仅公开的特定功能导出:
{
"name": "my-package",
"exports": {
".": "./lib/index.js",
"./feature/*.js": "./feature/*.js",
"./feature/internal/*": null
}
}
主要入口点导出
编写新包时,建议使用 "exports"
字段:
{
"exports": "./index.js"
}
当定义了 "exports"
字段时,包的所有子路径都被封装,导入程序将无法再访问它们。例如,require('pkg/subpath.js')
将抛出 ERR_PACKAGE_PATH_NOT_EXPORTED
错误。
这种导出的封装为工具以及处理包的语义版本升级提供了更可靠的包接口保证。但这并不是强封装,因为直接 require 包的任何绝对子路径,例如 require('/path/to/node_modules/pkg/subpath.js')
,仍然可以加载 subpath.js
。
所有当前支持的 Node.js 版本和现代构建工具都支持 "exports"
字段。对于使用旧版 Node.js 或相关构建工具的项目,可以通过同时包含 "main"
字段并将其指向与 "exports"
相同的模块来实现兼容性:
{
"main": "./index.js",
"exports": "./index.js"
}
子路径导出
新增于:v12.7.0
使用 "exports"
字段时,可以通过将主入口点视为 "."
子路径来定义自定义子路径以及主入口点:
{
"exports": {
".": "./index.js",
"./submodule.js": "./src/submodule.js"
}
}
现在,只有 "exports"
中定义的子路径才能被使用者导入:
import submodule from 'es-module-package/submodule.js'
// 加载 ./node_modules/es-module-package/src/submodule.js
而其他子路径将会报错:
import submodule from 'es-module-package/private-module.js'
// 抛出 ERR_PACKAGE_PATH_NOT_EXPORTED
子路径中的扩展名
包作者应该在其导出的内容中提供带扩展名 (import 'pkg/subpath.js'
) 或不带扩展名 (import 'pkg/subpath'
) 的子路径。这确保每个导出的模块只有一个子路径,以便所有依赖项都导入相同的、一致的标识符,从而使包契约对使用者清晰明了,并简化包子路径的补全。
传统上,包倾向于使用不带扩展名的样式,它具有可读性和隐藏包内文件真实路径的优点。
随着 导入映射 现在为浏览器和其他 JavaScript 运行时提供了一种包解析的标准,使用不带扩展名的样式会导致导入映射定义膨胀。显式文件扩展名可以通过允许导入映射利用 包文件夹映射 来映射多个子路径(如果可能),而不是每个包子路径导出都创建一个单独的映射条目,从而避免此问题。这也反映了在相对和绝对导入标识符中使用 完整标识符路径 的要求。
Exports 语法糖
新增于:v12.11.0
如果 "."
导出是唯一的导出,则 "exports"
字段为此情况提供语法糖,其值直接为 "exports"
字段的值。
{
"exports": {
".": "./index.js"
}
}
可以写成:
{
"exports": "./index.js"
}
子路径导入
新增于:v14.6.0, v12.19.0
除了 "exports"
字段外,还有一个包 "imports"
字段用于创建仅适用于包内导入说明符的私有映射。
"imports"
字段中的条目必须始终以 #
开头,以确保它们与外部包说明符区分开来。
例如,"imports"
字段可以用来获得内部模块条件导出的好处:
// 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
。
与 "exports"
字段不同,"imports"
字段允许映射到外部包。
"imports"
字段的解析规则与 "exports"
字段类似。
子路径模式
[历史]
版本 | 变更 |
---|---|
v16.10.0, v14.19.0 | 支持 "imports" 字段中的模式后缀。 |
v16.9.0, v14.19.0 | 支持模式后缀。 |
v14.13.0, v12.20.0 | 新增于:v14.13.0, v12.20.0 |
对于导出或导入数量较少的包,我们建议显式列出每个导出子路径条目。但是,对于具有大量子路径的包,这可能会导致 package.json
文件膨胀和维护问题。
对于这些用例,可以使用子路径导出模式:
// ./node_modules/es-module-package/package.json
{
"exports": {
"./features/*.js": "./src/features/*.js"
},
"imports": {
"#internal/*.js": "./src/internal/*.js"
}
}
*
映射公开嵌套子路径,因为它只是一个字符串替换语法。
右侧所有 *
实例都将被此值替换,包括包含任何 /
分隔符的情况。
import featureX from 'es-module-package/features/x.js'
// 加载 ./node_modules/es-module-package/src/features/x.js
import featureY from 'es-module-package/features/y/y.js'
// 加载 ./node_modules/es-module-package/src/features/y/y.js
import internalZ from '#internal/z.js'
// 加载 ./node_modules/es-module-package/src/internal/z.js
这是一个直接的静态匹配和替换,没有任何对文件扩展名的特殊处理。在映射的两侧都包含 "*.js"
将公开的包导出限制为只有 JS 文件。
由于可以通过将右侧目标模式视为针对包内文件列表的 **
glob 来确定包的各个导出,因此导出的静态可枚举属性在导出模式中得以保持。因为在导出目标中禁止使用 node_modules
路径,所以此扩展仅依赖于包本身的文件。
要从模式中排除私有子文件夹,可以使用 null
目标:
// ./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'
// 抛出:ERR_PACKAGE_PATH_NOT_EXPORTED
import featureX from 'es-module-package/features/x.js'
// 加载 ./node_modules/es-module-package/src/features/x.js
条件化导出
[历史]
版本 | 变更 |
---|---|
v13.7.0, v12.16.0 | 取消条件化导出的标志位。 |
v13.2.0, v12.16.0 | 新增于:v13.2.0, v12.16.0 |
条件化导出提供了一种根据特定条件映射到不同路径的方法。它同时支持 CommonJS 和 ES 模块导入。
例如,一个包想要为 require()
和 import
提供不同的 ES 模块导出,可以这样编写:
// package.json
{
"exports": {
"import": "./index-module.js",
"require": "./index-require.cjs"
},
"type": "module"
}
Node.js 实现以下条件,按从最具体到最不具体的顺序排列,因为条件应该按顺序定义:
"node-addons"
- 类似于"node"
,匹配任何 Node.js 环境。此条件可用于提供使用原生 C++ 插件的入口点,而不是更通用的且不依赖于原生插件的入口点。此条件可以通过--no-addons
标志 禁用。"node"
- 匹配任何 Node.js 环境。可以是 CommonJS 或 ES 模块文件。在大多数情况下,显式调用 Node.js 平台是没有必要的。"import"
- 当包通过import
或import()
加载时,或者通过 ECMAScript 模块加载器的任何顶级导入或解析操作加载时匹配。无论目标文件的模块格式如何,都适用。始终与"require"
互斥。"require"
- 当包通过require()
加载时匹配。引用的文件应该可以使用require()
加载,尽管条件匹配与目标文件的模块格式无关。预期的格式包括 CommonJS、JSON、原生插件和 ES 模块。始终与"import"
互斥。"module-sync"
- 无论包是通过import
、import()
还是require()
加载都匹配。格式应为 ES 模块,其模块图中不包含顶级 await - 如果包含,则在使用require()
加载模块时将抛出ERR_REQUIRE_ASYNC_MODULE
。"default"
- 始终匹配的通用回退。可以是 CommonJS 或 ES 模块文件。此条件应始终放在最后。
在 "exports"
对象中,键的顺序很重要。在条件匹配期间,较早的条目具有更高的优先级,优先于较后的条目。一般规则是条件应该按照从最具体到最不具体的顺序排列在对象中。
使用 "import"
和 "require"
条件可能会导致一些问题,这在双重 CommonJS/ES 模块包部分 中有进一步解释。
"node-addons"
条件可用于提供使用原生 C++ 插件的入口点。但是,此条件可以通过 --no-addons
标志 禁用。使用 "node-addons"
时,建议将 "default"
视为提供更通用入口点的增强功能,例如使用 WebAssembly 而不是原生插件。
条件化导出还可以扩展到导出子路径,例如:
{
"exports": {
".": "./index.js",
"./feature.js": {
"node": "./feature-node.js",
"default": "./feature.js"
}
}
}
定义一个包,其中 require('pkg/feature.js')
和 import 'pkg/feature.js'
可以在 Node.js 和其他 JS 环境之间提供不同的实现。
使用环境分支时,应尽可能包含 "default"
条件。提供 "default"
条件可确保任何未知的 JS 环境能够使用此通用实现,这有助于避免这些 JS 环境不得不假装成现有环境才能支持具有条件导出的包。因此,使用 "node"
和 "default"
条件分支通常优于使用 "node"
和 "browser"
条件分支。
嵌套条件
除了直接映射之外,Node.js 还支持嵌套条件对象。
例如,要定义一个仅包含双模式入口点的包,用于 Node.js 而非浏览器:
{
"exports": {
"node": {
"import": "./feature-node.mjs",
"require": "./feature-node.cjs"
},
"default": "./feature.mjs"
}
}
与扁平条件一样,条件会按顺序继续匹配。如果嵌套条件没有任何映射,它将继续检查父条件的其余条件。这样,嵌套条件的行为类似于嵌套的 JavaScript if
语句。
解析用户条件
新增于:v14.9.0, v12.19.0
运行 Node.js 时,可以使用 --conditions
标志添加自定义用户条件:
node --conditions=development index.js
这将解析包导入和导出中的 "development"
条件,同时根据需要解析现有的 "node"
、"node-addons"
、"default"
、"import"
和 "require"
条件。
可以使用重复标志设置任意数量的自定义条件。
典型的条件应该只包含字母数字字符,必要时使用 ":", "-", 或 "=" 作为分隔符。其他任何字符都可能在 Node.js 之外的环境中遇到兼容性问题。
在 Node.js 中,条件的限制很少,但具体包括:
社区条件定义
除了 Node.js 核心代码中实现的 "import"
、"require"
、"node"
、"module-sync"
、"node-addons"
和 "default"
条件 Node.js 核心代码实现 外的其他条件字符串默认情况下会被忽略。
其他平台可能会实现其他条件,并且可以通过 Node.js 中的 --conditions
/ -C
标志 启用用户条件。
由于自定义包条件需要明确的定义才能确保正确使用,因此下面提供了一个常用已知包条件及其严格定义的列表,以帮助协调生态系统。
"types"
- 类型系统可以使用它来解析给定导出的类型文件。此条件应始终放在首位。"browser"
- 任何 Web 浏览器环境。"development"
- 可用于定义仅限开发环境的入口点,例如,在开发模式下运行时提供额外的调试上下文,例如更好的错误消息。必须始终与"production"
互斥。"production"
- 可用于定义生产环境的入口点。必须始终与"development"
互斥。
对于其他运行时,平台特定的条件键定义由 WinterCG 在 运行时键 建议规范中维护。
可以通过向 此部分的 Node.js 文档 创建拉取请求来向此列表添加新的条件定义。在此处列出新的条件定义的要求是:
- 定义对于所有实现者都应该清晰明了。
- 需要该条件的原因及其用例应有清晰的理由。
- 应该存在足够的现有实现用法。
- 条件名称不应与另一个条件定义或广泛使用的条件冲突。
- 列出条件定义应该为生态系统提供协调优势,否则这是不可能的。例如,对于公司特定或应用程序特定的条件,情况并非如此。
- 该条件应该是一个 Node.js 用户期望在 Node.js 核心文档中看到的条件。
"types"
条件就是一个很好的例子:它实际上并不属于 运行时键 建议,但非常适合此处 Node.js 文档。
上述定义可能会在适当的时候迁移到一个专门的条件注册表。
使用包名自引用包
[历史]
版本 | 变更 |
---|---|
v13.6.0, v12.16.0 | 取消使用包名自引用包的限制标志。 |
v13.1.0, v12.16.0 | v13.1.0, v12.16.0 版本新增 |
在一个包内,可以通过包名引用package.json
的"exports"
字段中定义的值。例如,假设package.json
如下:
// package.json
{
"name": "a-package",
"exports": {
".": "./index.mjs",
"./foo.js": "./foo.js"
}
}
那么该包中的任何模块都可以引用包本身中的导出:
// ./a-module.mjs
import { something } from 'a-package' // 从 ./index.mjs 导入 "something"。
只有当package.json
具有"exports"
时,自引用才可用,并且只允许导入"exports"
(在package.json
中)允许的内容。因此,鉴于之前的包,下面的代码将生成运行时错误:
// ./another-module.mjs
// 从 ./m.mjs 导入 "another"。失败是因为
// "package.json" 的 "exports" 字段
// 没有提供名为 "./m.mjs" 的导出。
import { another } from 'a-package/m.mjs'
在 ES 模块和 CommonJS 模块中使用 require
时,也可用自引用。例如,这段代码也能运行:
// ./a-module.js
const { something } = require('a-package/foo.js') // 从 ./foo.js 加载。
最后,自引用也适用于作用域包。例如,这段代码也能运行:
// 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 模块包
详情参见 包示例仓库。
Node.js package.json
字段定义
本节描述 Node.js 运行时使用的字段。其他工具(例如 npm)使用 Node.js 忽略且此处未记录的其他字段。
package.json
文件中的以下字段在 Node.js 中使用:
"name"
- 在包内使用命名导入时相关。也用作包管理器的包名称。"main"
- 加载包时的默认模块,如果未指定 exports,以及在引入 exports 之前的 Node.js 版本中。"packageManager"
- 向包贡献时推荐的包管理器。由 Corepack shim 利用。"type"
- 包类型,用于确定是将.js
文件加载为 CommonJS 模块还是 ES 模块。"exports"
- 包导出和条件导出。如果存在,则限制可以从包内加载哪些子模块。"imports"
- 包导入,供包内的模块本身使用。
"name"
[历史]
版本 | 变更 |
---|---|
v13.6.0, v12.16.0 | 删除 --experimental-resolve-self 选项。 |
v13.1.0, v12.16.0 | 新增于:v13.1.0, v12.16.0 |
- 类型: <字符串>
{
"name": "package-name"
}
"name"
字段定义了你的包的名称。发布到 npm 注册表需要一个满足 特定要求 的名称。
"name"
字段可以与 "exports"
字段一起使用来 自引用 使用其名称的包。
"main"
新增于:v0.4.0
- 类型: <字符串>
{
"main": "./index.js"
}
"main"
字段定义了通过 node_modules
查找按名称导入的包的入口点。其值是一个路径。
当一个包具有 "exports"
字段时,在按名称导入包时,这将优先于 "main"
字段。
它还定义了当 包目录通过 require()
加载 时使用的脚本。
// 这将解析为 ./path/to/directory/index.js。
require('./path/to/directory')
"packageManager"
新增于:v16.9.0, v14.19.0
- 类型:<string>
{
"packageManager": "<package manager name>@<version>"
}
"packageManager"
字段定义了在当前项目中预期使用的包管理器。它可以设置为任何受支持的包管理器,并确保您的团队使用完全相同的包管理器版本,而无需安装 Node.js 之外的任何其他内容。
此字段目前处于实验阶段,需要选择加入;有关该过程的详细信息,请查看Corepack页面。
"type"
[历史]
版本 | 变更 |
---|---|
v13.2.0, v12.17.0 | 取消 --experimental-modules 标志。 |
v12.0.0 | 新增于:v12.0.0 |
- 类型:<string>
"type"
字段定义了 Node.js 用于所有具有该 package.json
文件作为其最近父级的 .js
文件的模块格式。
当最近的父级 package.json
文件包含一个值为 "module"
的顶级字段 "type"
时,以 .js
结尾的文件将加载为 ES 模块。
最近的父级 package.json
定义为在当前文件夹、该文件夹的父文件夹等中搜索时找到的第一个 package.json
,直到到达 node_modules 文件夹或卷根目录。
// package.json
{
"type": "module"
}
# 与前面的 package.json 在同一文件夹中 {#in-same-folder-as-preceding-packagejson}
node my-app.js # 作为 ES 模块运行
如果最近的父级 package.json
缺少 "type"
字段,或者包含 "type": "commonjs"
,则 .js
文件将被视为CommonJS。如果到达卷根目录并且没有找到 package.json
,则 .js
文件将被视为CommonJS。
如果最近的父级 package.json
包含 "type": "module"
,则 .js
文件的 import
语句将被视为 ES 模块。
// my-app.js,与上述示例相同
import './startup.js' // 由于 package.json 而被加载为 ES 模块
无论 "type"
字段的值如何,.mjs
文件始终被视为 ES 模块,而 .cjs
文件始终被视为 CommonJS。
"exports"
[历史]
版本 | 变更 |
---|---|
v14.13.0, v12.20.0 | 添加对 "exports" 模式的支持。 |
v13.7.0, v12.17.0 | 取消条件导出标志。 |
v13.7.0, v12.16.0 | 实现逻辑条件导出排序。 |
v13.7.0, v12.16.0 | 删除 --experimental-conditional-exports 选项。在 12.16.0 中,条件导出仍在 --experimental-modules 之后。 |
v13.2.0, v12.16.0 | 实现条件导出。 |
v12.7.0 | 新增于:v12.7.0 |
- 类型: <Object> | <string> | <string[]>
{
"exports": "./index.js"
}
"exports"
字段允许定义包的入口点,当通过名称导入时,通过 node_modules
查找或自引用 到其自身名称加载。它在 Node.js 12+ 中受支持,作为 "main"
的替代方案,可以支持定义子路径导出 和条件导出,同时封装内部未导出的模块。
条件导出 也可在 "exports"
中使用,以针对不同环境定义不同的包入口点,包括包是通过 require
还是通过 import
引用的。
"exports"
中定义的所有路径必须是相对于 ./
开头的相对文件 URL。
"imports"
新增于:v14.6.0, v12.19.0
- 类型: <Object>
// package.json
{
"imports": {
"#dep": {
"node": "dep-node-native",
"default": "./dep-polyfill.js"
}
},
"dependencies": {
"dep-node-native": "^1.0.0"
}
}
imports
字段中的条目必须是以 #
开头的字符串。
包导入允许映射到外部包。
此字段定义当前包的 子路径导入。