diff --git a/doc/api/errors.md b/doc/api/errors.md index 3151e3bd4675cb..e37b31652638c8 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1391,13 +1391,13 @@ An invalid or unknown file encoding was passed. ### `ERR_INVALID_PACKAGE_CONFIG` -An invalid `package.json` file was found which failed parsing. +An invalid [`package.json`][] file was found which failed parsing. ### `ERR_INVALID_PACKAGE_TARGET` -The `package.json` [exports][] field contains an invalid target mapping value -for the attempted module resolution. +The [`package.json`][] [`"exports"`][] field contains an invalid target mapping +value for the attempted module resolution. ### `ERR_INVALID_PERFORMANCE_MARK` @@ -1730,15 +1730,16 @@ A given value is out of the accepted range. ### `ERR_PACKAGE_IMPORT_NOT_DEFINED` -The `package.json` ["imports" field][] does not define the given internal +The [`package.json`][] [`"imports"`][] field does not define the given internal package specifier mapping. ### `ERR_PACKAGE_PATH_NOT_EXPORTED` -The `package.json` [exports][] field does not export the requested subpath. -Because exports are encapsulated, private internal modules that are not exported -cannot be imported through the package resolution, unless using an absolute URL. +The [`package.json`][] [`"exports"`][] field does not export the requested +subpath. Because exports are encapsulated, private internal modules that are not +exported cannot be imported through the package resolution, unless using an +absolute URL. ### `ERR_PROTO_ACCESS` @@ -2125,7 +2126,7 @@ signal (such as [`subprocess.kill()`][]). `import` a directory URL is unsupported. Instead, [self-reference a package using its name][] and [define a custom subpath][] in -the `"exports"` field of the `package.json` file. +the [`"exports"`][] field of the [`package.json`][] file. ```js @@ -2645,7 +2646,8 @@ closed. [crypto digest algorithm]: crypto.html#crypto_crypto_gethashes [domains]: domain.html [event emitter-based]: events.html#events_class_eventemitter -[exports]: packages.html#packages_package_entry_points +[`package.json`]: packages.html#packages_package_json_supported_fields +[`"exports"`]: packages.html#packages_exports [file descriptors]: https://en.wikipedia.org/wiki/File_descriptor [policy]: policy.html [RFC 7230 Section 3]: https://tools.ietf.org/html/rfc7230#section-3 @@ -2656,4 +2658,4 @@ closed. [vm]: vm.html [self-reference a package using its name]: packages.html#packages_self_referencing_a_package_using_its_name [define a custom subpath]: packages.html#packages_subpath_exports -["imports" field]: packages.html#packages_internal_package_imports +[`"imports"`]: packages.html#packages_imports diff --git a/doc/api/esm.md b/doc/api/esm.md index 32713f09d301fb..1c60639af57d3d 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -45,7 +45,7 @@ Expect major changes in the implementation including interoperability support, specifier resolution, and default behavior. - + @@ -55,7 +55,7 @@ specifier resolution, and default behavior. Node.js treats JavaScript code as CommonJS modules by default. Authors can tell Node.js to treat JavaScript code as ECMAScript modules -via the `.mjs` file extension, the `package.json` `"type"` field, or the +via the `.mjs` file extension, the `package.json` [`"type"`][] field, or the `--input-type` flag. See [Modules: Packages](packages.html#packages_determining_module_system) for more details. @@ -253,9 +253,9 @@ can either be an URL-style relative path like `'./file.mjs'` or a package name like `'fs'`. Like in CommonJS, files within packages can be accessed by appending a path to -the package name; unless the package’s `package.json` contains an `"exports"` -field, in which case files within packages need to be accessed via the path -defined in `"exports"`. +the package name; unless the package’s [`package.json`][] contains an +[`"exports"`][] field, in which case files within packages need to be accessed +via the path defined in [`"exports"`][]. ```js import { sin, cos } from 'geometry/trigonometry-functions.mjs'; @@ -933,7 +933,7 @@ The resolver can throw the following errors: > 1. If the folder at _packageURL_ does not exist, then > 1. Set _parentURL_ to the parent URL path of _parentURL_. > 1. Continue the next loop iteration. -> 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_packageURL_). +> 1. Let _pjson_ be the result of **READ_packages**(_packageURL_). > 1. If _pjson_ is not **null** and _pjson_._exports_ is not **null** or > **undefined**, then > 1. Let _exports_ be _pjson.exports_. @@ -953,7 +953,7 @@ The resolver can throw the following errors: > 1. Let _packageURL_ be the result of **READ_PACKAGE_SCOPE**(_parentURL_). > 1. If _packageURL_ is **null**, then > 1. Return **undefined**. -> 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_packageURL_). +> 1. Let _pjson_ be the result of **READ_packages**(_packageURL_). > 1. If _pjson_ is **null** or if _pjson_._exports_ is **null** or > **undefined**, then > 1. Return **undefined**. @@ -995,7 +995,7 @@ The resolver can throw the following errors: > 1. Throw an _Invalid Module Specifier_ error. > 1. Let _packageURL_ be the result of **READ_PACKAGE_SCOPE**(_parentURL_). > 1. If _packageURL_ is not **null**, then -> 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_packageURL_). +> 1. Let _pjson_ be the result of **READ_packages**(_packageURL_). > 1. If _pjson.imports_ is a non-null Object, then > 1. Let _resolvedMatch_ be the result of > **PACKAGE_IMPORTS_EXPORTS_RESOLVE**(_specifier_, _pjson.imports_, @@ -1091,12 +1091,12 @@ _conditions_) > 1. While _scopeURL_ is not the file system root, > 1. Set _scopeURL_ to the parent URL of _scopeURL_. > 1. If _scopeURL_ ends in a _"node_modules"_ path segment, return **null**. -> 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_scopeURL_). +> 1. Let _pjson_ be the result of **READ_packages**(_scopeURL_). > 1. If _pjson_ is not **null**, then > 1. Return _pjson_. > 1. Return **null**. -**READ_PACKAGE_JSON**(_packageURL_) +**READ_packages**(_packageURL_) > 1. Let _pjsonURL_ be the resolution of _"package.json"_ within _packageURL_. > 1. If the file at _pjsonURL_ does not exist, then @@ -1159,3 +1159,6 @@ success! [6.1.7 Array Index]: https://tc39.es/ecma262/#integer-index [Top-Level Await]: https://github.com/tc39/proposal-top-level-await [Core modules]: modules.html#modules_core_modules +[`package.json`]: packages.html#packages_package_json_supported_fields +[`"exports"`]: packages.html#packages_exports +[`"type"`]: packages.html#packages_type diff --git a/doc/api/modules.md b/doc/api/modules.md index 5edb1e101e2b3d..3bb921025570e4 100644 --- a/doc/api/modules.md +++ b/doc/api/modules.md @@ -391,8 +391,8 @@ directories, and then provide a single entry point to those directories. There are three ways in which a folder may be passed to `require()` as an argument. -The first is to create a `package.json` file in the root of the folder, -which specifies a `main` module. An example `package.json` file might +The first is to create a [`package.json`][] file in the root of the folder, +which specifies a `main` module. An example [`package.json`][] file might look like this: ```json @@ -406,10 +406,10 @@ If this was in a folder at `./some-library`, then This is the extent of the awareness of `package.json` files within Node.js. -If there is no `package.json` file present in the directory, or if the -`'main'` entry is missing or cannot be resolved, then Node.js +If there is no [`package.json`][] file present in the directory, or if the +[`"main"`][] entry is missing or cannot be resolved, then Node.js will attempt to load an `index.js` or `index.node` file out of that -directory. For example, if there was no `package.json` file in the above +directory. For example, if there was no [`package.json`][] file in the above example, then `require('./some-library')` would attempt to load: * `./some-library/index.js` @@ -986,3 +986,5 @@ This section was moved to [module resolution]: #modules_all_together [native addons]: addons.html [`require.main`]: #modules_require_main +[`package.json`]: packages.html#packages_package_json_supported_fields +[`"main"`]: packages.html#packages_main diff --git a/doc/api/packages.md b/doc/api/packages.md index c8fbf6c213bb83..9760925687adef 100644 --- a/doc/api/packages.md +++ b/doc/api/packages.md @@ -10,38 +10,170 @@ A folder containing a `package.json` file, and all subfolders below that folder until the next folder containing another `package.json` file, are a _package scope_. -## Determining module system +## `package.json` supported fields + +This document aims to describe the fields used by the Node.js +runtime. Other tools (such as +[npm](https://docs.npmjs.com/creating-a-package-json-file)) may use additional +fields which are ignored by Node.js and not documented here. + +### `"name"` + -Node.js will treat the following as [ES modules][] when passed to `node` as the -initial input, or when referenced by `import` statements within ES module code: +```json +{ + "name": "package-name" +} +``` -* Files ending in `.mjs`. +* Type: {string} + +The `"name"` field defines your package’s name. Node.js doesn't apply any +restriction on the name field, although the field is ignored if it is not a +string or an empty string. + +The `"name"` field can be used in addition to the [`"exports"`][] field to +[self-reference a package using its name][]. + +### `"exports"` + -* Files ending in `.js` when the nearest parent `package.json` file contains a - top-level field `"type"` with a value of `"module"`. +```json +{ + "exports": "./index.js" +} +``` -* Strings passed in as an argument to `--eval`, or piped to `node` via `STDIN`, - with the flag `--input-type=module`. +* Type: {Object} | {string} | {string[]} -Node.js will treat as [CommonJS][] all other forms of input, such as `.js` files -where the nearest parent `package.json` file contains no top-level `"type"` -field, or string input without the flag `--input-type`. This behavior is to -preserve backward compatibility. However, now that Node.js supports both -CommonJS and ES modules, it is best to be explicit whenever possible. Node.js -will treat the following as CommonJS when passed to `node` as the initial input, -or when referenced by `import` statements within ES module code: +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"` when referencing the +package by its name. -* Files ending in `.cjs`. +[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`. -* Files ending in `.js` when the nearest parent `package.json` file contains a - top-level field `"type"` with a value of `"commonjs"`. +All paths defined in the `"exports"` must be relative file URLs starting with +`./`. -* Strings passed in as an argument to `--eval` or `--print`, or piped to `node` - via `STDIN`, with the flag `--input-type=commonjs`. +### `"main"` + + +```json +{ + "main": "./main.js" +} +``` + +* Type: {string} + +The `"main"` field defines the script that is used when the +current directory is required by another script. Its value is interpreted as a +path. + +```js +require('./path/to/directory'); // This resolves to ./path/to/directory/main.js. +``` + +This field is ignored when referencing the package by its name and the +[`"exports"`][] field is provided. 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 directory. + +### `"imports"` + + +* Type: {Object} + +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. + +Entries in the imports field must always start with `#` to ensure they are +clearly disambiguated from package specifiers. + +For example, the imports field can be used to gain the benefits of conditional +exports for internal modules: + +```json +// package.json +{ + "imports": { + "#dep": { + "node": "dep-node-native", + "default": "./dep-polyfill.js" + } + }, + "dependencies": { + "dep-node-native": "^1.0.0" + } +} +``` + +where `import '#dep'` would now get the resolution of the external package +`dep-node-native` (including its exports in turn), and instead get the local +file `./dep-polyfill.js` relative to the package in other environments. + +Unlike the exports field, import maps permit mapping to external packages +because this provides an important use case for conditional loading and also can +be done without the risk of cycles, unlike for exports. + +Apart from the above, the resolution rules for the imports field are otherwise +analogous to the exports field. + +### `"type"` + -### `package.json` `"type"` field +* Type: {string} -Files ending with `.js` will be loaded as [ES modules][] when the nearest parent +The `"type"` field defines how `.js` files should be treated +within a particular `package.json` file’s package scope. + +Files ending with `.js` will be loaded as ES modules when the nearest parent `package.json` file contains a top-level field `"type"` with a value of `"module"`. @@ -49,26 +181,22 @@ The nearest parent `package.json` is defined as the first `package.json` found when searching in the current folder, that folder’s parent, and so on up until the root of the volume is reached. - -```js +```json // package.json { "type": "module" } ``` -```bash -# In same folder as preceding package.json +```console +# In same folder as above package.json node my-app.js # Runs as ES module ``` If the nearest parent `package.json` lacks a `"type"` field, or contains -`"type": "commonjs"`, `.js` files are treated as [CommonJS][]. If the volume -root is reached and no `package.json` is found, Node.js defers to the default, a -`package.json` with no `"type"` field. - -`import` statements of `.js` files are treated as ES modules if the nearest -parent `package.json` contains `"type": "module"`. +`"type": "commonjs"`, or any other unsupported value, `.js` files are treated as +CommonJS. If the volume root is reached and no `package.json` is found, Node.js +defers to the default, a `package.json` with no `"type"` field (CommonJS). ```js // my-app.js, part of the same example as above @@ -76,13 +204,42 @@ import './startup.js'; // Loaded as ES module because of package.json ``` Package authors should include the `"type"` field, even in packages where all -sources are [CommonJS][]. Being explicit about the `type` of the package will +sources are CommonJS. Being explicit about the `type` of the package will future-proof the package in case the default type of Node.js ever changes, and it will also make things easier for build tools and loaders to determine how the files in the package should be interpreted. Regardless of the value of the `"type"` field, `.mjs` files are always treated -as ES modules and `.cjs` files are always treated as [CommonJS][]. +as ES modules and `.cjs` files are always treated as CommonJS. + +## Determining module system + +Node.js will treat the following as [ES modules][] when passed to `node` as the +initial input, or when referenced by `import` statements within ES module code: + +* Files ending in `.mjs`. + +* Files ending in `.js` when the nearest parent `package.json` file contains a + top-level field [`"type"`][] with a value of `"module"`. + +* Strings passed in as an argument to `--eval`, or piped to `node` via `STDIN`, + with the flag `--input-type=module`. + +Node.js will treat as [CommonJS][] all other forms of input, such as `.js` files +where the nearest parent `package.json` file contains no top-level `"type"` +field, or string input without the flag `--input-type`. This behavior is to +preserve backward compatibility. However, now that Node.js supports both +CommonJS and ES modules, it is best to be explicit whenever possible. Node.js +will treat the following as CommonJS when passed to `node` as the initial input, +or when referenced by `import` statements within ES module code: + +* Files ending in `.cjs`. + +* Files ending in `.js` when the nearest parent `package.json` file contains a + top-level field [`"type"`][] with a value of `"commonjs"`. + +* Strings passed in as an argument to `--eval` or `--print`, or piped to `node` + via `STDIN`, with the flag `--input-type=commonjs`. ### Package scope and file extensions @@ -158,35 +315,35 @@ unspecified. ## 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. +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"`**. +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"` 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. +If both [`"exports"`][] and [`"main"`][] are defined, the [`"exports"`][] field +takes 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 +[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.** +**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 +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`, @@ -236,7 +393,7 @@ path `import feature from 'my-mod/feature/index.js`. ### 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: +[`"exports"`][] and [`"main"`][] in the package’s [`package.json`][] file: ```json { @@ -245,7 +402,7 @@ To set the main entry point for a package, it is advisable to define both } ``` -The benefit of doing this is that when using the `"exports"` field all +The benefit of doing this is that when using the [`"exports"`][] field all subpaths of the package will no longer be available to importers under `require('pkg/subpath.js')`, and instead they will get a new error, `ERR_PACKAGE_PATH_NOT_EXPORTED`. @@ -260,7 +417,7 @@ absolute subpath of the package such as > Stability: 1 - Experimental -When using the `"exports"` field, custom subpaths can be defined along +When using the [`"exports"`][] field, custom subpaths can be defined along with the main entry point by treating the main entry point as the `"."` subpath: @@ -274,8 +431,7 @@ with the main entry point by treating the main entry point as the } ``` -Now only the defined subpath in `"exports"` can be imported by a -consumer: +Now only the defined subpath in [`"exports"`][] can be imported by a consumer: ```js import submodule from 'es-module-package/submodule'; @@ -335,11 +491,11 @@ instead as the fallback, as if it were the only target. > Stability: 1 - Experimental -If the `"."` export is the only export, the `"exports"` field provides sugar -for this case being the direct `"exports"` field value. +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. +If the `"."` export has a fallback array or string value, then the +[`"exports"`][] field can be set to this value directly. ```json { @@ -394,7 +550,7 @@ Node.js supports the following conditions out of the box: * `"default"` - the generic fallback that will always match. Can be a CommonJS or ES module file. _This condition should always come last._ -Within the `"exports"` object, key order is significant. During condition +Within the [`"exports"`][] object, key order is significant. During condition matching, earlier entries have higher priority and take precedence over later entries. _The general rule is that conditions should be from most specific to least specific in object order_. @@ -479,7 +635,7 @@ Any number of custom conditions can be set with repeat flags. ### Self-referencing a package using its name Within a package, the values defined in the package’s -`package.json` `"exports"` field can be referenced via the package’s name. +`package.json` [`"exports"`][] field can be referenced via the package’s name. For example, assuming the `package.json` is: ```json @@ -500,9 +656,10 @@ Then any module _in that package_ can reference an export in the package itself: import { something } from 'a-package'; // Imports "something" from ./main.mjs. ``` -Self-referencing is available only if `package.json` has `exports`, and will -allow importing only what that `exports` (in the `package.json`) allows. -So the code below, given the previous package, will generate a runtime error: +Self-referencing is available only if `package.json` has [`"exports"`][], and +will allow importing only what that [`"exports"`][] (in the `package.json`) +allows. So the code below, given the package above, will generate a runtime +error: ```js // ./another-module.mjs @@ -521,51 +678,13 @@ and in a CommonJS one. For example, this code will also work: const { something } = require('a-package/foo'); // Loads from ./foo.js. ``` -## Internal package imports - -> Stability: 1 - Experimental - -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. - -Entries in the imports field must always start with `#` to ensure they are -clearly disambiguated from package specifiers. - -For example, the imports field can be used to gain the benefits of conditional -exports for internal modules: - -```json -// package.json -{ - "imports": { - "#dep": { - "node": "dep-node-native", - "default": "./dep-polyfill.js" - } - }, - "dependencies": { - "dep-node-native": "^1.0.0" - } -} -``` - -where `import '#dep'` would now get the resolution of the external package -`dep-node-native` (including its exports in turn), and instead get the local -file `./dep-polyfill.js` relative to the package in other environments. - -Unlike the exports field, import maps permit mapping to external packages -because this provides an important use case for conditional loading and also can -be done without the risk of cycles, unlike for exports. - -Apart from the above, the resolution rules for the imports field are otherwise -analogous to the exports field. - ## Dual CommonJS/ES module packages Prior to the introduction of support for ES modules in Node.js, it was a common pattern for package authors to include both CommonJS and ES module JavaScript -sources in their package, with `package.json` `"main"` specifying the CommonJS -entry point and `package.json` `"module"` specifying the ES module entry point. +sources in their package, with `package.json` [`"main"`][] specifying the +CommonJS entry point and `package.json` `"module"` specifying the ES module +entry point. This enabled Node.js to run the CommonJS entry point while build tools such as bundlers used the ES module entry point, since Node.js ignored (and still ignores) the top-level `"module"` field. @@ -719,7 +838,7 @@ stateless): #### Approach #2: Isolate state -A `package.json` file can define the separate CommonJS and ES module entry +A [`package.json`][] file can define the separate CommonJS and ES module entry points directly: ```json @@ -826,3 +945,8 @@ conditional exports for consumers could be to add an export, e.g. [the dual CommonJS/ES module packages section]: #packages_dual_commonjs_es_module_packages [ES modules]: esm.html [CommonJS]: modules.html +[`package.json`]: #packages_package_json_supported_fields +[`"exports"`]: #packages_exports +[`"main"`]: #packages_main +[`"type"`]: #packages_type +[self-reference a package using its name]: #packages_self_referencing_a_package_using_its_name