Skip to content

Commit

Permalink
doc: explicitly doc package.exports is breaking
Browse files Browse the repository at this point in the history
If package authors don't explicitly include all previously supported
entry points introducing package.exports will be a Semver-Major change.

Add a warning about this behavior and offer two potential solutions
for module authors.

Refs: then/is-promise#20

PR-URL: #33074
Reviewed-By: Guy Bedford <[email protected]>
Reviewed-By: Jan Krems <[email protected]>
Reviewed-By: Geoffrey Booth <[email protected]>
Reviewed-By: Anna Henningsen <[email protected]>
  • Loading branch information
MylesBorins committed May 5, 2020
1 parent 26f1500 commit 1ffd182
Showing 1 changed file with 66 additions and 15 deletions.
81 changes: 66 additions & 15 deletions doc/api/esm.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,25 +183,75 @@ 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"`. If package entry
points are defined in both `"main"` and `"exports"`, the latter takes precedence
in versions of Node.js that support `"exports"`. [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`.
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"`.
precedence over `"main"`. `"exports"` are not specific to ES modules or
CommonJS; `"main"` will be 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`:

Both `"main"` and `"exports"` entry points are not specific to ES modules or
CommonJS; `"main"` will be overridden by `"exports"` in a `require` so it is
not a CommonJS fallback.
```json
{
"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:

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

This is important with regard to `require`, since `require` of ES module files
throws an error in all versions of Node.js. To create a package that works both
in modern Node.js via `import` and `require` and also legacy Node.js versions,
see [the dual CommonJS/ES module packages section][].
As a last resort, package encapsulation can be disabled entirely by creating an
export for the root of the package `"./": "./"`. This will expose 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
will encapsulation be lost but module consumers will be unable to
`import feature from 'my-mod/feature'` as they will need to provide the full
path `import feature from 'my-mod/feature/index.js`.

#### Main Entry Point Export

Expand Down Expand Up @@ -1746,6 +1796,7 @@ success!
[dynamic instantiate hook]: #esm_code_dynamicinstantiate_code_hook
[import an ES or CommonJS module for its side effects only]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Import_a_module_for_its_side_effects_only
[special scheme]: https://url.spec.whatwg.org/#special-scheme
[the full specifier path]: #esm_mandatory_file_extensions
[the official standard format]: https://tc39.github.io/ecma262/#sec-modules
[the dual CommonJS/ES module packages section]: #esm_dual_commonjs_es_module_packages
[transpiler loader example]: #esm_transpiler_loader
Expand Down

0 comments on commit 1ffd182

Please sign in to comment.