包的入口
在包的 package.json
文件中,有两个字段可以定义包的入口点:"main"
和 "exports"
。
所有版本的 Node.js 都支持 "main"
字段,但它的功能有限:它只定义了包的主要入口点。
"exports"
字段提供了 "main"
的替代方案,其中可以定义包主入口点,同时封装包,防止除 "exports"
中定义的入口点之外的任何其他入口点。
这种封装允许模块作者为他们的包定义一个公共接口。
如果同时定义了 "exports"
和 "main"
,则 "exports"
字段优先于 "main"
。
"exports"
不特定于 ES 模块或 CommonJS;如果 "exports"
存在,则 "main"
将被覆盖。
因此 "main"
不能用作 CommonJS 的后备,但它可以用作不支持 "exports"
字段的旧版 Node.js 的后备。
条件导出可以在 "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-mod",
"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.js": "./feature/index.js",
"./package.json": "./package.json"
}
}
或者,一个项目可以选择导出整个文件夹:
{
"name": "my-mod",
"exports": {
".": "./lib/index.js",
"./lib": "./lib/index.js",
"./lib/*": "./lib/*.js",
"./feature": "./feature/index.js",
"./feature/*": "./feature/*.js",
"./package.json": "./package.json"
}
}
作为最后的手段,可以通过为包 "./*": "./*"
的根创建导出来完全禁用包封装。
这会以禁用封装和潜在的工具优势为代价公开包中的每个文件。
由于 Node.js 中的 ES 模块加载器强制使用完整说明符路径,导出根而不是明确表示条目比前面的任何一个示例都没有表现力。
不仅封装丢失,模块消费者也无法 import feature from 'my-mod/feature'
,因为他们需要提供完整路径 import feature from 'my-mod/feature/index.js
。
In a package’s package.json
file, two fields can define entry points for a
package: "main"
and "exports"
. 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.
The "exports"
field provides an alternative to "main"
where the
package main entry point can be defined while also encapsulating the package,
preventing any other entry points besides those defined in "exports"
.
This encapsulation allows module authors to define a public interface for
their package.
If both "exports"
and "main"
are defined, the "exports"
field
takes precedence over "main"
. "exports"
are not specific to ES
modules or CommonJS; "main"
is overridden by "exports"
if it
exists. As such "main"
cannot be used as a fallback for CommonJS but it
can be used as a fallback for legacy versions of Node.js that do not support the
"exports"
field.
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.
Warning: Introducing the "exports"
field prevents consumers of a
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.
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 previous exported main
, lib
,
feature
, and the package.json
could use the following package.exports
:
{
"name": "my-mod",
"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.js": "./feature/index.js",
"./package.json": "./package.json"
}
}
Alternatively a project could choose to export entire folders:
{
"name": "my-mod",
"exports": {
".": "./lib/index.js",
"./lib": "./lib/index.js",
"./lib/*": "./lib/*.js",
"./feature": "./feature/index.js",
"./feature/*": "./feature/*.js",
"./package.json": "./package.json"
}
}
As a last resort, package encapsulation can be disabled entirely by creating an
export for the root of the package "./*": "./*"
. This exposes every file
in the package at the cost of disabling the encapsulation and potential tooling
benefits this provides. As the ES Module loader in Node.js enforces the use of
the full specifier path, exporting the root rather than being explicit
about entry is less expressive than either of the prior examples. Not only
is encapsulation lost but module consumers are unable to
import feature from 'my-mod/feature'
as they need to provide the full
path import feature from 'my-mod/feature/index.js
.