diff --git a/doc/api/packages.md b/doc/api/packages.md index ec388fa17d25fe..02a9091206ab06 100644 --- a/doc/api/packages.md +++ b/doc/api/packages.md @@ -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 + +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 -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,8 +461,8 @@ 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 @@ -406,6 +470,17 @@ analogous to the exports field. 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 - - - -> 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 - - - -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 + +> 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