Skip to content

Commit 07372e9

Browse files
MylesBorinscodebytere
authored andcommitted
doc: explicitly doc package.exports is breaking
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]>
1 parent cc02c73 commit 07372e9

File tree

1 file changed

+66
-15
lines changed

1 file changed

+66
-15
lines changed

doc/api/esm.md

+66-15
Original file line numberDiff line numberDiff line change
@@ -183,25 +183,75 @@ versions of Node.js, but its capabilities are limited: it only defines the main
183183
entry point of the package.
184184

185185
The `"exports"` field provides an alternative to `"main"` where the package
186-
main entry point can be defined while also encapsulating the package, preventing
187-
any other entry points besides those defined in `"exports"`. If package entry
188-
points are defined in both `"main"` and `"exports"`, the latter takes precedence
189-
in versions of Node.js that support `"exports"`. [Conditional Exports][] can
190-
also be used within `"exports"` to define different package entry points per
191-
environment, including whether the package is referenced via `require` or via
192-
`import`.
186+
main entry point can be defined while also encapsulating the package,
187+
**preventing any other entry points besides those defined in `"exports"`**.
188+
This encapsulation allows module authors to define a public interface for
189+
their package.
193190

194191
If both `"exports"` and `"main"` are defined, the `"exports"` field takes
195-
precedence over `"main"`.
192+
precedence over `"main"`. `"exports"` are not specific to ES modules or
193+
CommonJS; `"main"` will be overridden by `"exports"` if it exists. As such
194+
`"main"` cannot be used as a fallback for CommonJS but it can be used as a
195+
fallback for legacy versions of Node.js that do not support the `"exports"`
196+
field.
197+
198+
[Conditional Exports][] can be used within `"exports"` to define different
199+
package entry points per environment, including whether the package is
200+
referenced via `require` or via `import`. For more information about supporting
201+
both CommonJS and ES Modules in a single package please consult
202+
[the dual CommonJS/ES module packages section][].
203+
204+
**Warning**: Introducing the `"exports"` field prevents consumers of a package
205+
from using any entry points that are not defined, including the `package.json`
206+
(e.g. `require('your-package/package.json')`. **This will likely be a breaking
207+
change.**
208+
209+
To make the introduction of `"exports"` non-breaking, ensure that every
210+
previously supported entry point is exported. It is best to explicitly specify
211+
entry points so that the package’s public API is well-defined. For example,
212+
a project that previous exported `main`, `lib`,
213+
`feature`, and the `package.json` could use the following `package.exports`:
196214

197-
Both `"main"` and `"exports"` entry points are not specific to ES modules or
198-
CommonJS; `"main"` will be overridden by `"exports"` in a `require` so it is
199-
not a CommonJS fallback.
215+
```json
216+
{
217+
"name": "my-mod",
218+
"exports": {
219+
".": "./lib/index.js",
220+
"./lib": "./lib/index.js",
221+
"./lib/index": "./lib/index.js",
222+
"./lib/index.js": "./lib/index.js",
223+
"./feature": "./feature/index.js",
224+
"./feature/index.js": "./feature/index.js",
225+
"./package.json": "./package.json"
226+
}
227+
}
228+
```
229+
230+
Alternatively a project could choose to export entire folders:
231+
232+
```json
233+
{
234+
"name": "my-mod",
235+
"exports": {
236+
".": "./lib/index.js",
237+
"./lib": "./lib/index.js",
238+
"./lib/": "./lib/",
239+
"./feature": "./feature/index.js",
240+
"./feature/": "./feature/",
241+
"./package.json": "./package.json"
242+
}
243+
}
244+
```
200245

201-
This is important with regard to `require`, since `require` of ES module files
202-
throws an error in all versions of Node.js. To create a package that works both
203-
in modern Node.js via `import` and `require` and also legacy Node.js versions,
204-
see [the dual CommonJS/ES module packages section][].
246+
As a last resort, package encapsulation can be disabled entirely by creating an
247+
export for the root of the package `"./": "./"`. This will expose every file in
248+
the package at the cost of disabling the encapsulation and potential tooling
249+
benefits this provides. As the ES Module loader in Node.js enforces the use of
250+
[the full specifier path][], exporting the root rather than being explicit
251+
about entry is less expressive than either of the prior examples. Not only
252+
will encapsulation be lost but module consumers will be unable to
253+
`import feature from 'my-mod/feature'` as they will need to provide the full
254+
path `import feature from 'my-mod/feature/index.js`.
205255

206256
#### Main Entry Point Export
207257

@@ -1746,6 +1796,7 @@ success!
17461796
[dynamic instantiate hook]: #esm_code_dynamicinstantiate_code_hook
17471797
[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
17481798
[special scheme]: https://url.spec.whatwg.org/#special-scheme
1799+
[the full specifier path]: #esm_mandatory_file_extensions
17491800
[the official standard format]: https://tc39.github.io/ecma262/#sec-modules
17501801
[the dual CommonJS/ES module packages section]: #esm_dual_commonjs_es_module_packages
17511802
[transpiler loader example]: #esm_transpiler_loader

0 commit comments

Comments
 (0)