From e1d4107ce20161a1182cfdb7d75333bdae87ca59 Mon Sep 17 00:00:00 2001 From: Izaak Schroeder Date: Mon, 10 Jul 2023 16:37:14 -0700 Subject: [PATCH] esm: add `initialize` hook Refs: https://github.com/nodejs/loaders/issues/147 --- doc/api/esm.md | 72 +++++++++- doc/api/module.md | 21 +++ lib/internal/modules/esm/hooks.js | 48 +++++-- lib/internal/modules/esm/loader.js | 27 ++-- lib/internal/modules/esm/utils.js | 10 ++ lib/internal/process/pre_execution.js | 13 +- test/es-module/test-esm-loader-hooks.mjs | 136 ++++++++++++++++++ .../hooks-initialize-port.mjs | 17 +++ .../es-module-loaders/hooks-initialize.mjs | 3 + 9 files changed, 323 insertions(+), 24 deletions(-) create mode 100644 test/fixtures/es-module-loaders/hooks-initialize-port.mjs create mode 100644 test/fixtures/es-module-loaders/hooks-initialize.mjs diff --git a/doc/api/esm.md b/doc/api/esm.md index 68ecad93b8ad3b8..9e3718241c686c4 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -685,6 +685,12 @@ of Node.js applications. + +> The loaders API is being redesigned. This hook may disappear or its +> signature may change. Do not rely on the API described below. + +> In a previous version of this API, this was split across 3 separate, now +> deprecated, hooks (`getFormat`, `getSource`, and `transformSource`). + +* `data` {any} The data provided via `Module.register(loader, parentUrl, data)` +* Returns: {any} The data returned to the caller of `Module.register` + +The `initialize` hook provides a way to define a custom method of that runs +in the loader's thread when the loader is initialized. This hook can send +and receive data from a `Module.register` invocation, including ports and +other transferrable objects. + +Loader code: + +```js +// In the below example this file is referenced as +// '/path-to-my-loader.js' + +export function initialize({ number, port }) { + port.postMessage(`increment: ${number + 1}`); + return 'ok'; +} +``` + +Caller code: + +```js +import assert from 'node:assert'; +import { register } from 'node:module'; +import { MessageChannel } from 'node:worker_threads'; + +// In this example '/path-to-my-loader.js' is replaced with the +// path to the file containing the loader contents above. + +// This example showcases how a message channel can be used to +// communicate to the loader, by sending `port2` to the loader. +const { port1, port2 } = new MessageChannel(); + +port1.on('message', (msg) => { + assert(msg === 'increment: 2'); +}); + +const result = register('/path-to-my-loader.js', import.meta.url, { + data: { number: 1, port: port2 }, + transferList: [port2], +}); + +assert(result === 'ok'); +``` + #### `globalPreload()` -> The loaders API is being redesigned. This hook may disappear or its -> signature may change. Do not rely on the API described below. +> Deprecated: Use `initialize` instead. > In a previous version of this API, this hook was named > `getGlobalPreloadCode`. diff --git a/doc/api/module.md b/doc/api/module.md index 8dd5fd4fa59f6c1..3193f1da6c1ab1e 100644 --- a/doc/api/module.md +++ b/doc/api/module.md @@ -175,6 +175,27 @@ globalPreload: http-to-https globalPreload: unpkg ``` +This function can also be used to pass data to the loader's `initialize` +hook include transferrable objects like ports. + +```mjs +import { register } from 'node:module'; +import { MessageChannel } from 'node:worker_threads'; + +// This example showcases how a message channel can be used to +// communicate to the loader, by sending `port2` to the loader. +const { port1, port2 } = new MessageChannel(); + +port1.on('message', (msg) => { + console.log(msg); +}); + +register('./my-programmatic-loader.mjs', import.meta.url, { + data: { number: 1, port: port2 }, + transferList: [port2], +}); +``` + ### `module.syncBuiltinESMExports()`