diff --git a/doc/api/errors.md b/doc/api/errors.md index 09f866fc2869b5..da2335ae275d68 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1267,6 +1267,23 @@ provided. Encoding provided to `TextDecoder()` API was not one of the [WHATWG Supported Encodings][]. + + +### `ERR_ESM_LOADER_REGISTRATION_UNAVAILABLE` + + + +Programmatically registering custom ESM loaders +currently requires at least one custom loader to have been +registered via the `--experimental-loader` flag. A no-op +loader registered via CLI is sufficient +(for example: `--experimental-loader data:text/javascript,`; +do not omit the necessary trailing comma). +A future version of Node.js will support the programmatic +registration of loaders without needing to also use the flag. + ### `ERR_EVAL_ESM_CANNOT_PRINT` diff --git a/doc/api/esm.md b/doc/api/esm.md index 3d450ce3c69310..68ecad93b8ad3b 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -1225,6 +1225,17 @@ console.log('some module!'); If you run `node --experimental-loader ./import-map-loader.js main.js` the output will be `some module!`. +### Register loaders programmatically + + + +In addition to using the `--experimental-loader` option in the CLI, +loaders can also be registered programmatically. You can find +detailed information about this process in the documentation page +for [`module.register()`][]. + ## Resolution and loading algorithm ### Features @@ -1599,6 +1610,7 @@ for ESM specifiers is [commonjs-extension-resolution-loader][]. [`import.meta.url`]: #importmetaurl [`import`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import [`module.createRequire()`]: module.md#modulecreaterequirefilename +[`module.register()`]: module.md#moduleregister [`module.syncBuiltinESMExports()`]: module.md#modulesyncbuiltinesmexports [`package.json`]: packages.md#nodejs-packagejson-field-definitions [`port.ref()`]: https://nodejs.org/dist/latest-v17.x/docs/api/worker_threads.html#portref diff --git a/doc/api/module.md b/doc/api/module.md index d52ec34dd12a54..f3752f3f81a5b2 100644 --- a/doc/api/module.md +++ b/doc/api/module.md @@ -80,6 +80,101 @@ isBuiltin('fs'); // true isBuiltin('wss'); // false ``` +### `module.register()` + + + +In addition to using the `--experimental-loader` option in the CLI, +loaders can be registered programmatically using the +`module.register()` method. + +```mjs +import { register } from 'node:module'; + +register('http-to-https', import.meta.url); + +// Because this is a dynamic `import()`, the `http-to-https` hooks will run +// before importing `./my-app.mjs`. +await import('./my-app.mjs'); +``` + +In the example above, we are registering the `http-to-https` loader, +but it will only be available for subsequently imported modules—in +this case, `my-app.mjs`. If the `await import('./my-app.mjs')` had +instead been a static `import './my-app.mjs'`, _the app would already +have been loaded_ before the `http-to-https` hooks were +registered. This is part of the design of ES modules, where static +imports are evaluated from the leaves of the tree first back to the +trunk. There can be static imports _within_ `my-app.mjs`, which +will not be evaluated until `my-app.mjs` is when it's dynamically +imported. + +The `--experimental-loader` flag of the CLI can be used together +with the `register` function; the loaders registered with the +function will follow the same evaluation chain of loaders registered +within the CLI: + +```console +node \ + --experimental-loader unpkg \ + --experimental-loader http-to-https \ + --experimental-loader cache-buster \ + entrypoint.mjs +``` + +```mjs +// entrypoint.mjs +import { URL } from 'node:url'; +import { register } from 'node:module'; + +const loaderURL = new URL('./my-programmatically-loader.mjs', import.meta.url); + +register(loaderURL); +await import('./my-app.mjs'); +``` + +The `my-programmatic-loader.mjs` can leverage `unpkg`, +`http-to-https`, and `cache-buster` loaders. + +It's also possible to use `register` more than once: + +```mjs +// entrypoint.mjs +import { URL } from 'node:url'; +import { register } from 'node:module'; + +register(new URL('./first-loader.mjs', import.meta.url)); +register('./second-loader.mjs', import.meta.url); +await import('./my-app.mjs'); +``` + +Both loaders (`first-loader.mjs` and `second-loader.mjs`) can use +all the resources provided by the loaders registered in the CLI. But +remember that they will only be available in the next imported +module (`my-app.mjs`). The evaluation order of the hooks when +importing `my-app.mjs` and consecutive modules in the example above +will be: + +```console +resolve: second-loader.mjs +resolve: first-loader.mjs +resolve: cache-buster +resolve: http-to-https +resolve: unpkg +load: second-loader.mjs +load: first-loader.mjs +load: cache-buster +load: http-to-https +load: unpkg +globalPreload: second-loader.mjs +globalPreload: first-loader.mjs +globalPreload: cache-buster +globalPreload: http-to-https +globalPreload: unpkg +``` + ### `module.syncBuiltinESMExports()`