包的入口


在包的 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。 例如,以前导出 mainlibfeaturepackage.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.