From f91babe49409c4dd9d1093959522c84f48e0576a Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Fri, 10 Jun 2022 18:38:58 -0700 Subject: [PATCH] doc: packages documentation updates for 12 EOL PR-URL: https://github.com/nodejs/node/pull/43375 Reviewed-By: Geoffrey Booth Reviewed-By: Antoine du Hamel --- doc/api/packages.md | 257 ++++++++++++++++++++++++++------------------ 1 file changed, 150 insertions(+), 107 deletions(-) diff --git a/doc/api/packages.md b/doc/api/packages.md index f5eadab65220f9..2dce76f8539c43 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,48 +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 ``` -### 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