Skip to content

Commit

Permalink
doc: packages documentation updates for 12 EOL
Browse files Browse the repository at this point in the history
PR-URL: nodejs/node#43375
Backport-PR-URL: nodejs/node#43809
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
guybedford authored and guangwong committed Oct 10, 2022
1 parent 7a32176 commit 8e507e8
Showing 1 changed file with 195 additions and 152 deletions.
347 changes: 195 additions & 152 deletions doc/api/packages.md
Original file line number Diff line number Diff line change
@@ -225,31 +225,32 @@ in your project's `package.json`.
## Package entry points

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.
package: [`"main"`][] and [`"exports"`][]. Both fields apply to both ES module
and CommonJS module entry points.

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"`][] 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.

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.

[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
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
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.**

@@ -261,54 +262,61 @@ a project that previous exported `main`, `lib`,

```json
{
"name": "my-mod",
"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:
Alternatively a project could choose to export entire folders both with and
without extensioned subpaths using export patterns:

```json
{
"name": "my-mod",
"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"
}
}
```

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`.
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:

```json
{
"name": "my-package",
"exports": {
".": "./lib/index.js",
"./feature/*.js": "./feature/*.js",
"./feature/internal/*": null
}
}
```

### Main entry point export

To set the main entry point for a package, it is advisable to define both
[`"exports"`][] and [`"main"`][] in the package's [`package.json`][] file:
When writing a new package, it is recommended to use the [`"exports"`][] field:

```json
{
"main": "./main.js",
"exports": "./main.js"
"exports": "./index.js"
}
```

@@ -323,6 +331,18 @@ 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`.

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:

```json
{
"main": "./index.js",
"exports": "./index.js"
}
```

### Subpath exports

<!-- YAML
@@ -335,18 +355,17 @@ with the main entry point by treating the main entry point as the

```json
{
"main": "./main.js",
"exports": {
".": "./main.js",
"./submodule": "./src/submodule.js"
".": "./index.js",
"./submodule.js": "./src/submodule.js"
}
}
```

Now only the defined subpath in [`"exports"`][] can be imported by a consumer:

```js
import submodule from 'es-module-package/submodule';
import submodule from 'es-module-package/submodule.js';
// Loads ./node_modules/es-module-package/src/submodule.js
```

@@ -357,6 +376,51 @@ import submodule from 'es-module-package/private-module.js';
// Throws ERR_PACKAGE_PATH_NOT_EXPORTED
```

#### Extensions in subpaths

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.

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

<!-- YAML
added: v12.11.0
-->

If the `"."` export is the only export, the [`"exports"`][] field provides sugar
for this case being the direct [`"exports"`][] field value.

```json
{
"exports": {
".": "./index.js"
}
}
```

can be written:

```json
{
"exports": "./index.js"
}
```

### Subpath imports

<!-- YAML
@@ -365,12 +429,12 @@ added:
- v12.19.0
-->

In addition to the [`"exports"`][] field, it is possible to define internal
package import maps that only apply to import specifiers from within the package
itself.
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.

Entries in the imports field must always start with `#` to ensure they are
disambiguated from package specifiers.
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:
@@ -397,15 +461,26 @@ file `./dep-polyfill.js` relative to the package in other environments.
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.
The resolution rules for the imports field are otherwise analogous to the
exports field.

### Subpath patterns

<!-- YAML
added:
- v14.13.0
- v12.20.0
changes:
- version:
- v16.10.0
- v14.19.0
pr-url: https://github.com/nodejs/node/pull/40041
description: Support pattern trailers in "imports" field.
- version:
- v16.9.0
- v14.19.0
pr-url: https://github.com/nodejs/node/pull/39635
description: Support pattern trailers.
-->

For packages with a small number of exports or imports, we recommend
@@ -419,10 +494,10 @@ For these use cases, subpath export patterns can be used instead:
// ./node_modules/es-module-package/package.json
{
"exports": {
"./features/*": "./src/features/*.js"
"./features/*.js": "./src/features/*.js"
},
"imports": {
"#internal/*": "./src/internal/*.js"
"#internal/*.js": "./src/internal/*.js"
}
}
```
@@ -434,19 +509,19 @@ All instances of `*` on the right hand side will then be replaced with this
value, including if it contains any `/` separators.

```js
import featureX from 'es-module-package/features/x';
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';
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';
import internalZ from '#internal/z.js';
// Loads ./node_modules/es-module-package/src/internal/z.js
```

This is a direct static replacement without any special handling for file
extensions. In the previous example, `pkg/features/x.json` would be resolved to
`./src/features/x.json.js` in the mapping.
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.

The property of exports being statically enumerable is maintained with exports
patterns since the individual exports for a package can be determined by
@@ -460,93 +535,20 @@ To exclude private subfolders from patterns, `null` targets can be used:
// ./node_modules/es-module-package/package.json
{
"exports": {
"./features/*": "./src/features/*.js",
"./features/*.js": "./src/features/*.js",
"./features/private-internal/*": null
}
}
```

```js
import featureInternal from 'es-module-package/features/private-internal/m';
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';
import featureX from 'es-module-package/features/x.js';
// Loads ./node_modules/es-module-package/src/features/x.js
```

### Subpath folder mappings

<!-- YAML
changes:
- version: v16.0.0
pr-url: https://github.com/nodejs/node/pull/37215
description: Runtime deprecation.
- version: v15.1.0
pr-url: https://github.com/nodejs/node/pull/35747
description: Runtime deprecation for self-referencing imports.
- version:
- v14.13.0
- v12.20.0
pr-url: https://github.com/nodejs/node/pull/34718
description: Documentation-only deprecation.
-->

> Stability: 0 - Deprecated: Use subpath patterns instead.
Before subpath patterns were supported, a trailing `"/"` suffix was used to
support folder mappings:

```json
{
"exports": {
"./features/": "./features/"
}
}
```

_This feature will be removed in a future release._

Instead, use direct [subpath patterns][]:

```json
{
"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.

### Exports sugar

<!-- YAML
added: v12.11.0
-->

If the `"."` export is the only export, the [`"exports"`][] field provides sugar
for this case being the direct [`"exports"`][] field value.

If the `"."` export has a fallback array or string value, then the
[`"exports"`][] field can be set to this value directly.

```json
{
"exports": {
".": "./main.js"
}
}
```

can be written:

```json
{
"exports": "./main.js"
}
```

### Conditional exports

<!-- YAML
@@ -570,10 +572,9 @@ For example, a package that wants to provide different ES module exports for
```json
// package.json
{
"main": "./main-require.cjs",
"exports": {
"import": "./main-module.js",
"require": "./main-require.cjs"
"import": "./index-module.js",
"require": "./index-require.cjs"
},
"type": "module"
}
@@ -621,20 +622,19 @@ Conditional exports can also be extended to exports subpaths, for example:

```json
{
"main": "./main.js",
"exports": {
".": "./main.js",
"./feature": {
".": "./index.js",
"./feature.js": {
"node": "./feature-node.js",
"default": "./feature.js"
}
}
}
```

Defines a package where `require('pkg/feature')` and `import 'pkg/feature'`
could provide different implementations between Node.js and other JS
environments.
Defines a package where `require('pkg/feature.js')` and
`import 'pkg/feature.js'` could provide different implementations between
Node.js and other JS environments.

When using environment branches, always include a `"default"` condition where
possible. Providing a `"default"` condition ensures that any unknown JS
@@ -653,7 +653,6 @@ use in Node.js but not the browser:

```json
{
"main": "./main.js",
"exports": {
"node": {
"import": "./feature-node.mjs",
@@ -681,7 +680,7 @@ When running Node.js, custom user conditions can be added with the
`--conditions` flag:

```bash
node --conditions=development main.js
node --conditions=development index.js
```

which would then resolve the `"development"` condition in package imports and
@@ -754,8 +753,8 @@ For example, assuming the `package.json` is:
{
"name": "a-package",
"exports": {
".": "./main.mjs",
"./foo": "./foo.js"
".": "./index.mjs",
"./foo.js": "./foo.js"
}
}
```
@@ -764,7 +763,7 @@ Then any module _in that package_ can reference an export in the package itself:

```js
// ./a-module.mjs
import { something } from 'a-package'; // Imports "something" from ./main.mjs.
import { something } from 'a-package'; // Imports "something" from ./index.mjs.
```

Self-referencing is available only if `package.json` has [`"exports"`][], and
@@ -786,7 +785,7 @@ and in a CommonJS one. For example, this code will also work:

```cjs
// ./a-module.js
const { something } = require('a-package/foo'); // Loads from ./foo.js.
const { something } = require('a-package/foo.js'); // Loads from ./foo.js.
```

Finally, self-referencing also works with scoped packages. For example, this
@@ -815,6 +814,51 @@ $ node other.js
42
```

### Subpath folder mappings

<!-- YAML
changes:
- version: v16.0.0
pr-url: https://github.com/nodejs/node/pull/37215
description: Runtime deprecation.
- version: v15.1.0
pr-url: https://github.com/nodejs/node/pull/35747
description: Runtime deprecation for self-referencing imports.
- version:
- v14.13.0
- v12.20.0
pr-url: https://github.com/nodejs/node/pull/34718
description: Documentation-only deprecation.
-->

> Stability: 0 - Deprecated: Use subpath patterns instead.
Before subpath patterns were supported, a trailing `"/"` suffix was used to
support folder mappings:

```json
{
"exports": {
"./features/": "./features/"
}
}
```

_This feature will be removed in a future release._

Instead, use direct [subpath patterns][]:

```json
{
"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.

## Dual CommonJS/ES module packages

Prior to the introduction of support for ES modules in Node.js, it was a common
@@ -899,7 +943,6 @@ CommonJS entry point for `require`.
// ./node_modules/pkg/package.json
{
"type": "module",
"main": "./index.cjs",
"exports": {
"import": "./wrapper.mjs",
"require": "./index.cjs"
@@ -967,7 +1010,6 @@ stateless):
// ./node_modules/pkg/package.json
{
"type": "module",
"main": "./index.cjs",
"exports": {
".": "./index.cjs",
"./module": "./wrapper.mjs"
@@ -984,7 +1026,6 @@ points directly:
// ./node_modules/pkg/package.json
{
"type": "module",
"main": "./index.cjs",
"exports": {
"import": "./index.mjs",
"require": "./index.cjs"
@@ -1070,7 +1111,6 @@ conditional exports for consumers could be to add an export, e.g.
// ./node_modules/pkg/package.json
{
"type": "module",
"main": "./index.cjs",
"exports": {
".": "./index.cjs",
"./module": "./index.mjs"
@@ -1138,7 +1178,7 @@ added: v0.4.0

```json
{
"main": "./main.js"
"main": "./index.js"
}
```

@@ -1152,7 +1192,8 @@ It also defines the script that is used when the [package directory is loaded
via `require()`](modules.md#folders-as-modules).

```cjs
require('./path/to/directory'); // This resolves to ./path/to/directory/main.js.
// This resolves to ./path/to/directory/index.js.
require('./path/to/directory');
```

### `"packageManager"`
@@ -1313,7 +1354,7 @@ added:

Entries in the imports field must be strings starting with `#`.

Import maps permit mapping to external packages.
Package imports permit mapping to external packages.

This field defines [subpath imports][] for the current package.

@@ -1337,8 +1378,10 @@ This field defines [subpath imports][] for the current package.
[`package.json`]: #nodejs-packagejson-field-definitions
[entry points]: #package-entry-points
[folders as modules]: modules.md#folders-as-modules
[import maps]: https://github.com/WICG/import-maps
[load ECMASCript modules from CommonJS modules]: modules.md#the-mjs-extension
[loader hooks]: esm.md#loaders
[packages folder mapping]: https://github.com/WICG/import-maps#packages-via-trailing-slashes
[self-reference]: #self-referencing-a-package-using-its-name
[subpath exports]: #subpath-exports
[subpath imports]: #subpath-imports

0 comments on commit 8e507e8

Please sign in to comment.