From 7335da6f447045f58d3d646667c4a0331e5c775d Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 16 Jul 2024 14:07:27 +0200 Subject: [PATCH 01/18] feat: Only instrument hooked modules --- hook.js | 10 ++++++++++ index.js | 18 ++++++++++++++++++ test/fixtures/import-after.mjs | 13 +++++++++++++ test/fixtures/import.mjs | 10 ++++++++++ test/register/v18.19-include-message-port.mjs | 3 +++ 5 files changed, 54 insertions(+) create mode 100644 test/fixtures/import-after.mjs create mode 100644 test/fixtures/import.mjs create mode 100644 test/register/v18.19-include-message-port.mjs diff --git a/hook.js b/hook.js index fb4d598..c1a282c 100644 --- a/hook.js +++ b/hook.js @@ -250,6 +250,16 @@ function createHook (meta) { if (data) { includeModules = ensureArrayWithBareSpecifiersAndFileUrls(data.include, 'include') excludeModules = ensureArrayWithBareSpecifiersAndFileUrls(data.exclude, 'exclude') + + if (data.addHookMessagePort) { + data.addHookMessagePort.on('message', (modules) => { + if (includeModules === undefined) { + includeModules = [] + } + + includeModules.push(...modules) + }) + } } } diff --git a/index.js b/index.js index 50aa04f..c36f470 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ const path = require('path') const parse = require('module-details-from-path') const { fileURLToPath } = require('url') +const { MessageChannel } = require('worker_threads') const { importHooks, @@ -31,6 +32,18 @@ function callHookFn (hookFn, namespace, name, baseDir) { } } +let sendToMessageChannel + +function createAddHookMessageChannel() { + const { port1, port2 } = new MessageChannel() + + sendToMessageChannel = (modules) => { + port1.postMessage(modules) + } + + return port2 +} + function Hook (modules, options, hookFn) { if ((this instanceof Hook) === false) return new Hook(modules, options, hookFn) if (typeof modules === 'function') { @@ -43,6 +56,10 @@ function Hook (modules, options, hookFn) { } const internals = options ? options.internals === true : false + if (sendToMessageChannel && Array.isArray(modules)) { + sendToMessageChannel(modules) + } + this._iitmHook = (name, namespace) => { const filename = name const isBuiltin = name.startsWith('node:') @@ -92,3 +109,4 @@ module.exports = Hook module.exports.Hook = Hook module.exports.addHook = addHook module.exports.removeHook = removeHook +module.exports.createAddHookMessageChannel = createAddHookMessageChannel diff --git a/test/fixtures/import-after.mjs b/test/fixtures/import-after.mjs new file mode 100644 index 0000000..1c1b0fd --- /dev/null +++ b/test/fixtures/import-after.mjs @@ -0,0 +1,13 @@ +import { strictEqual } from 'assert' +import { sep } from 'path' +import { Hook } from '../../index.js' + +const hooked = [] + +Hook((_, name) => { + hooked.push(name) +}) + +strictEqual(hooked.length, 1) +strictEqual(hooked[0], 'path') +strictEqual(sep, '@') diff --git a/test/fixtures/import.mjs b/test/fixtures/import.mjs new file mode 100644 index 0000000..392059a --- /dev/null +++ b/test/fixtures/import.mjs @@ -0,0 +1,10 @@ +import { register } from 'module' +import { Hook, createAddHookMessageChannel } from '../../index.js' + +const addHookMessagePort = createAddHookMessageChannel() + +register('../../hook.mjs', import.meta.url, { data: { addHookMessagePort }, transferList: [addHookMessagePort] }) + +Hook(['path'], (exports) => { + exports.sep = '@' +}) diff --git a/test/register/v18.19-include-message-port.mjs b/test/register/v18.19-include-message-port.mjs new file mode 100644 index 0000000..c4f21c0 --- /dev/null +++ b/test/register/v18.19-include-message-port.mjs @@ -0,0 +1,3 @@ +import { spawnSync } from 'child_process' + +spawnSync(process.execPath, ['--import', './test/fixtures/import.mjs', './test/fixtures/import-after.mjs'], { stdio: 'inherit' }) From bc8eb03ac6aba59450e72465aa847369c3de9d6e Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 16 Jul 2024 14:13:41 +0200 Subject: [PATCH 02/18] Fix test and lint --- index.js | 2 +- test/register/v18.19-include-message-port.mjs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index c36f470..3b44c72 100644 --- a/index.js +++ b/index.js @@ -34,7 +34,7 @@ function callHookFn (hookFn, namespace, name, baseDir) { let sendToMessageChannel -function createAddHookMessageChannel() { +function createAddHookMessageChannel () { const { port1, port2 } = new MessageChannel() sendToMessageChannel = (modules) => { diff --git a/test/register/v18.19-include-message-port.mjs b/test/register/v18.19-include-message-port.mjs index c4f21c0..08f208b 100644 --- a/test/register/v18.19-include-message-port.mjs +++ b/test/register/v18.19-include-message-port.mjs @@ -1,3 +1,7 @@ import { spawnSync } from 'child_process' -spawnSync(process.execPath, ['--import', './test/fixtures/import.mjs', './test/fixtures/import-after.mjs'], { stdio: 'inherit' }) +const out = spawnSync(process.execPath, + ['--import', './test/fixtures/import.mjs', './test/fixtures/import-after.mjs'], + { stdio: 'inherit', env: {} } +) +process.exit(out.status) From b001494ecbababa489697f19c70da3834e1d0809 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 16 Jul 2024 14:44:02 +0200 Subject: [PATCH 03/18] Update types --- index.d.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/index.d.ts b/index.d.ts index c468977..9d7f805 100644 --- a/index.d.ts +++ b/index.d.ts @@ -84,3 +84,21 @@ export declare function addHook(hookFn: HookFunction): void * @param {HookFunction} hookFn The function to be removed. */ export declare function removeHook(hookFn: HookFunction): void + +/** + * Creates a message channel with a port that can be used to add hooks to the + * list of exclusively included modules. + * + * This can be used to only wrap modules that are Hook'ed, however modules need + * to be hooked before they are imported. + * + * ```ts + * import { register } from 'module' + * import { createAddHookMessageChannel } from 'import-in-the-middle' + * + * const addHookMessagePort = createAddHookMessageChannel() + * + * register('import-in-the-middle/hook.mjs', import.meta.url, { data: { addHookMessagePort }, transferList: [addHookMessagePort] }) + * ``` + */ +export declare function createAddHookMessageChannel(): MessagePort; From 612abcfe4dfadf20439b49334f29ebb7ccf6cfec Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 16 Jul 2024 15:37:40 +0200 Subject: [PATCH 04/18] also import path to ensure it can still be hooked after --- test/fixtures/import.mjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/fixtures/import.mjs b/test/fixtures/import.mjs index 392059a..61a2dcf 100644 --- a/test/fixtures/import.mjs +++ b/test/fixtures/import.mjs @@ -1,5 +1,7 @@ import { register } from 'module' import { Hook, createAddHookMessageChannel } from '../../index.js' +// We've imported path here to ensure that the hook is still applied later. +import * as path from 'path' const addHookMessagePort = createAddHookMessageChannel() @@ -8,3 +10,5 @@ register('../../hook.mjs', import.meta.url, { data: { addHookMessagePort }, tran Hook(['path'], (exports) => { exports.sep = '@' }) + +console.assert(path.sep !== '@') From a92146a05627a4fb017394e6fa8e284acb2435b4 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 16 Jul 2024 16:28:21 +0200 Subject: [PATCH 05/18] Update readme --- README.md | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c746c63..8d489f3 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,11 @@ console.log(foo) // 1 more than whatever that module exported This requires the use of an ESM loader hook, which can be added with the following command-line option. -``` ---loader=import-in-the-middle/hook.mjs +```shell +node --loader=import-in-the-middle/hook.mjs my-app.mjs ``` -It's also possible to register the loader hook programmatically via the Node +Since `--loader` has been deprecated you cab also register the loader hook programmatically via the Node [`module.register()`](https://nodejs.org/api/module.html#moduleregisterspecifier-parenturl-options) API. However, for this to be able to hook non-dynamic imports, it needs to be loaded before your app code is evaluated via the `--import` command-line option. @@ -71,6 +71,40 @@ module.register('import-in-the-middle/hook.mjs', import.meta.url, { }) ``` +### Only Intercepting Hooked modules + +If you are `Hook`'ing all modules before they are imported, for example in a +module loaded via the Node.js `--import` argument, you can configure the loader hook to only intercept those specific modules: + +`instrument.mjs` +```js +import { register } from 'module' +import { Hook, createAddHookMessageChannel } from 'import-in-the-middle' + +const addHookMessagePort = createAddHookMessageChannel() + +const options = { + data: { addHookMessagePort }, + transferList: [addHookMessagePort] +} + +register('import-in-the-middle/hook.mjs', import.meta.url, options) + +Hook(['fs'], (exported, name, baseDir) => { + // Instrument the fs module +}) +``` +`my-app.mjs` +```js +import * as fs from 'fs' +// fs will be instrumented! +fs.readFileSync('file.txt') +``` + +```shell +node --import=./instrument.mjs ./my-app.mjs +``` + ## Limitations * You cannot add new exports to a module. You can only modify existing ones. From ab39976b4fd8a2f4940ecd1740d1c75b17b082ad Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 16 Jul 2024 17:43:21 +0200 Subject: [PATCH 06/18] typos --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8d489f3..3c35c43 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,10 @@ command-line option. node --loader=import-in-the-middle/hook.mjs my-app.mjs ``` -Since `--loader` has been deprecated you cab also register the loader hook programmatically via the Node +Since `--loader` has been deprecated you can also register the loader hook programmatically via the Node [`module.register()`](https://nodejs.org/api/module.html#moduleregisterspecifier-parenturl-options) API. However, for this to be able to hook non-dynamic imports, it needs to be -loaded before your app code is evaluated via the `--import` command-line option. +registered before your app code is evaluated via the `--import` command-line option. `my-loader.mjs` ```js From e9c7c4122dd906576754e230406b61bc25a095ae Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 16 Jul 2024 17:44:47 +0200 Subject: [PATCH 07/18] line breaks --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c35c43..86380b2 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,8 @@ module.register('import-in-the-middle/hook.mjs', import.meta.url, { ### Only Intercepting Hooked modules If you are `Hook`'ing all modules before they are imported, for example in a -module loaded via the Node.js `--import` argument, you can configure the loader hook to only intercept those specific modules: +module loaded via the Node.js `--import` argument, you can configure the loader +hook to only intercept those specific modules: `instrument.mjs` ```js From b7841380ca45970fb79b23f6a59d415f6e6e4c07 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 17 Jul 2024 01:38:43 +0200 Subject: [PATCH 08/18] Ensure all messages to loader are acknowledged --- README.md | 15 ++++++++------- hook.js | 5 +++-- index.js | 33 ++++++++++++++++++++++++++++----- test/fixtures/import.mjs | 7 +++++-- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 86380b2..e0197a2 100644 --- a/README.md +++ b/README.md @@ -74,26 +74,27 @@ module.register('import-in-the-middle/hook.mjs', import.meta.url, { ### Only Intercepting Hooked modules If you are `Hook`'ing all modules before they are imported, for example in a -module loaded via the Node.js `--import` argument, you can configure the loader -hook to only intercept those specific modules: +module loaded via the Node.js `--import` CLI argument, you can configure the +loader to intercept only modules that were specifically hooked. `instrument.mjs` ```js import { register } from 'module' import { Hook, createAddHookMessageChannel } from 'import-in-the-middle' -const addHookMessagePort = createAddHookMessageChannel() +const { addHookMessagePort, waitForAllMessagesAcknowledged } = createAddHookMessageChannel() -const options = { - data: { addHookMessagePort }, - transferList: [addHookMessagePort] -} +const options = { data: { addHookMessagePort }, transferList: [addHookMessagePort] } register('import-in-the-middle/hook.mjs', import.meta.url, options) Hook(['fs'], (exported, name, baseDir) => { // Instrument the fs module }) + +// Ensure that the loader has acknowledged all the modules +// before we allow execution to continue +await waitForAllMessagesAcknowledged() ``` `my-app.mjs` ```js diff --git a/hook.js b/hook.js index 3e19f98..5f67288 100644 --- a/hook.js +++ b/hook.js @@ -263,7 +263,8 @@ function createHook (meta) { } includeModules.push(...modules) - }) + data.addHookMessagePort.postMessage('ack') + }).unref() } } } @@ -271,7 +272,7 @@ function createHook (meta) { async function resolve (specifier, context, parentResolve) { cachedResolve = parentResolve - // See github.com/nodejs/import-in-the-middle/pull/76. + // See https://github.com/nodejs/import-in-the-middle/pull/76. if (specifier === iitmURL) { return { url: specifier, diff --git a/index.js b/index.js index 3b44c72..5c66a98 100644 --- a/index.js +++ b/index.js @@ -32,16 +32,39 @@ function callHookFn (hookFn, namespace, name, baseDir) { } } -let sendToMessageChannel +let sendModulesToLoader function createAddHookMessageChannel () { const { port1, port2 } = new MessageChannel() + let pendingAckCount = 0 + let resolveFn - sendToMessageChannel = (modules) => { + sendModulesToLoader = (modules) => { + pendingAckCount++ port1.postMessage(modules) } - return port2 + port1.on('message', () => { + pendingAckCount-- + + if (resolveFn && pendingAckCount <= 0) { + resolveFn() + } + }).unref() + + function waitForAllMessagesAcknowledged () { + const promise = new Promise((resolve) => { + resolveFn = resolve + }) + + if (pendingAckCount === 0) { + resolveFn() + } + + return promise + } + + return { addHookMessagePort: port2, waitForAllMessagesAcknowledged } } function Hook (modules, options, hookFn) { @@ -56,8 +79,8 @@ function Hook (modules, options, hookFn) { } const internals = options ? options.internals === true : false - if (sendToMessageChannel && Array.isArray(modules)) { - sendToMessageChannel(modules) + if (sendModulesToLoader && Array.isArray(modules)) { + sendModulesToLoader(modules) } this._iitmHook = (name, namespace) => { diff --git a/test/fixtures/import.mjs b/test/fixtures/import.mjs index 61a2dcf..aca9724 100644 --- a/test/fixtures/import.mjs +++ b/test/fixtures/import.mjs @@ -1,9 +1,10 @@ import { register } from 'module' import { Hook, createAddHookMessageChannel } from '../../index.js' -// We've imported path here to ensure that the hook is still applied later. +// We've imported path here to ensure that the hook is still applied later even +// if the library is used here. import * as path from 'path' -const addHookMessagePort = createAddHookMessageChannel() +const { addHookMessagePort, waitForAllMessagesAcknowledged } = createAddHookMessageChannel() register('../../hook.mjs', import.meta.url, { data: { addHookMessagePort }, transferList: [addHookMessagePort] }) @@ -12,3 +13,5 @@ Hook(['path'], (exports) => { }) console.assert(path.sep !== '@') + +await waitForAllMessagesAcknowledged() From d82c8ae8529baa13f5878dc61f211044886f43ee Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 17 Jul 2024 01:50:24 +0200 Subject: [PATCH 09/18] debug failing CI --- test/register/v18.19-include-message-port.mjs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/register/v18.19-include-message-port.mjs b/test/register/v18.19-include-message-port.mjs index 08f208b..17d8b2c 100644 --- a/test/register/v18.19-include-message-port.mjs +++ b/test/register/v18.19-include-message-port.mjs @@ -4,4 +4,11 @@ const out = spawnSync(process.execPath, ['--import', './test/fixtures/import.mjs', './test/fixtures/import-after.mjs'], { stdio: 'inherit', env: {} } ) + +if (out.error) { + console.error(out.error) +} +if (out.status !== 0) { + console.error(out.stderr.toString()) +} process.exit(out.status) From 708ed61679649c3bf78bbd29abdc7bf3dfafedae Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 17 Jul 2024 01:54:00 +0200 Subject: [PATCH 10/18] more CI --- test/register/v18.19-include-message-port.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/register/v18.19-include-message-port.mjs b/test/register/v18.19-include-message-port.mjs index 17d8b2c..e464a0a 100644 --- a/test/register/v18.19-include-message-port.mjs +++ b/test/register/v18.19-include-message-port.mjs @@ -9,6 +9,6 @@ if (out.error) { console.error(out.error) } if (out.status !== 0) { - console.error(out.stderr.toString()) + console.error(`Expected exit code 0, got ${out.status}`) } process.exit(out.status) From d41b0171ff24444e2984d8c277bc1233f26e6855 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 17 Jul 2024 02:16:58 +0200 Subject: [PATCH 11/18] Use a timer to stop exit code 13 --- index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 5c66a98..592a414 100644 --- a/index.js +++ b/index.js @@ -53,9 +53,12 @@ function createAddHookMessageChannel () { }).unref() function waitForAllMessagesAcknowledged () { + // This timer is to prevent the process from exiting with code 13: + // 13: Unsettled Top-Level Await. + const timer = setInterval(() => { }, 1000) const promise = new Promise((resolve) => { resolveFn = resolve - }) + }).then(() => { clearInterval(timer) }) if (pendingAckCount === 0) { resolveFn() From 35fdfe0cd5a4040a39fdde9dcdc0fd5bc4d3e935 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 17 Jul 2024 02:20:26 +0200 Subject: [PATCH 12/18] update types docs --- index.d.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/index.d.ts b/index.d.ts index 9d7f805..15f9a00 100644 --- a/index.d.ts +++ b/index.d.ts @@ -94,11 +94,21 @@ export declare function removeHook(hookFn: HookFunction): void * * ```ts * import { register } from 'module' - * import { createAddHookMessageChannel } from 'import-in-the-middle' + * import { Hook, createAddHookMessageChannel } from 'import-in-the-middle' * - * const addHookMessagePort = createAddHookMessageChannel() + * const { addHookMessagePort, waitForAllMessagesAcknowledged } = createAddHookMessageChannel() * - * register('import-in-the-middle/hook.mjs', import.meta.url, { data: { addHookMessagePort }, transferList: [addHookMessagePort] }) + * const options = { data: { addHookMessagePort }, transferList: [addHookMessagePort] } + * + * register('import-in-the-middle/hook.mjs', import.meta.url, options) + * + * Hook(['fs'], (exported, name, baseDir) => { + * // Instrument the fs module + * }) + * + * // Ensure that the loader has acknowledged all the modules + * // before we allow execution to continue + * await waitForAllMessagesAcknowledged() * ``` */ export declare function createAddHookMessageChannel(): MessagePort; From 323bfc20a0217fbc8f7592497a8d4b41a91f3b68 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 17 Jul 2024 10:33:06 +0200 Subject: [PATCH 13/18] Fix types --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 15f9a00..5e1cfe5 100644 --- a/index.d.ts +++ b/index.d.ts @@ -111,4 +111,4 @@ export declare function removeHook(hookFn: HookFunction): void * await waitForAllMessagesAcknowledged() * ``` */ -export declare function createAddHookMessageChannel(): MessagePort; +export declare function createAddHookMessageChannel(): { addHookMessagePort: MessagePort, waitForAllMessagesAcknowledged: Promise }; From b70a48ff0a475a49091971e82eefa8a2bcfc5c12 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 23 Jul 2024 15:54:00 +0200 Subject: [PATCH 14/18] Add `registerOptions` --- README.md | 6 ++---- index.d.ts | 8 +++++++- index.js | 5 ++++- test/fixtures/import.mjs | 4 ++-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 97b089b..84022ed 100644 --- a/README.md +++ b/README.md @@ -82,11 +82,9 @@ loader to intercept only modules that were specifically hooked. import { register } from 'module' import { Hook, createAddHookMessageChannel } from 'import-in-the-middle' -const { addHookMessagePort, waitForAllMessagesAcknowledged } = createAddHookMessageChannel() +const { registerOptions, waitForAllMessagesAcknowledged } = createAddHookMessageChannel() -const options = { data: { addHookMessagePort }, transferList: [addHookMessagePort] } - -register('import-in-the-middle/hook.mjs', import.meta.url, options) +register('import-in-the-middle/hook.mjs', import.meta.url, registerOptions) Hook(['fs'], (exported, name, baseDir) => { // Instrument the fs module diff --git a/index.d.ts b/index.d.ts index 5e1cfe5..260d8c1 100644 --- a/index.d.ts +++ b/index.d.ts @@ -85,6 +85,12 @@ export declare function addHook(hookFn: HookFunction): void */ export declare function removeHook(hookFn: HookFunction): void +type CreateAddHookMessageChannelReturn = { + addHookMessagePort: MessagePort, + waitForAllMessagesAcknowledged: Promise + registerOptions: { data?: Data; transferList?: any[]; } +} + /** * Creates a message channel with a port that can be used to add hooks to the * list of exclusively included modules. @@ -111,4 +117,4 @@ export declare function removeHook(hookFn: HookFunction): void * await waitForAllMessagesAcknowledged() * ``` */ -export declare function createAddHookMessageChannel(): { addHookMessagePort: MessagePort, waitForAllMessagesAcknowledged: Promise }; +export declare function createAddHookMessageChannel(): CreateAddHookMessageChannelReturn; diff --git a/index.js b/index.js index 592a414..0d7a66c 100644 --- a/index.js +++ b/index.js @@ -67,7 +67,10 @@ function createAddHookMessageChannel () { return promise } - return { addHookMessagePort: port2, waitForAllMessagesAcknowledged } + const addHookMessagePort = port2 + const registerOptions = { data: { addHookMessagePort, include: [] }, transferList: [addHookMessagePort] } + + return { registerOptions, addHookMessagePort, waitForAllMessagesAcknowledged } } function Hook (modules, options, hookFn) { diff --git a/test/fixtures/import.mjs b/test/fixtures/import.mjs index aca9724..96f109d 100644 --- a/test/fixtures/import.mjs +++ b/test/fixtures/import.mjs @@ -4,9 +4,9 @@ import { Hook, createAddHookMessageChannel } from '../../index.js' // if the library is used here. import * as path from 'path' -const { addHookMessagePort, waitForAllMessagesAcknowledged } = createAddHookMessageChannel() +const { registerOptions, waitForAllMessagesAcknowledged } = createAddHookMessageChannel() -register('../../hook.mjs', import.meta.url, { data: { addHookMessagePort }, transferList: [addHookMessagePort] }) +register('../../hook.mjs', import.meta.url, registerOptions) Hook(['path'], (exports) => { exports.sep = '@' From 3a510c4574c1bf0b57fe88673ff31a2db24c7bed Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 23 Jul 2024 16:07:06 +0200 Subject: [PATCH 15/18] Correct jsdoc in types --- index.d.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/index.d.ts b/index.d.ts index 260d8c1..683a63a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -102,11 +102,9 @@ type CreateAddHookMessageChannelReturn = { * import { register } from 'module' * import { Hook, createAddHookMessageChannel } from 'import-in-the-middle' * - * const { addHookMessagePort, waitForAllMessagesAcknowledged } = createAddHookMessageChannel() + * const { registerOptions, waitForAllMessagesAcknowledged } = createAddHookMessageChannel() * - * const options = { data: { addHookMessagePort }, transferList: [addHookMessagePort] } - * - * register('import-in-the-middle/hook.mjs', import.meta.url, options) + * register('import-in-the-middle/hook.mjs', import.meta.url, registerOptions) * * Hook(['fs'], (exported, name, baseDir) => { * // Instrument the fs module From 7299d2da31dc91422d36f424c9be009dfd99bd3c Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 23 Jul 2024 18:14:46 +0200 Subject: [PATCH 16/18] Add jsdoc to createAddHookMessageChannel --- index.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/index.js b/index.js index 0d7a66c..58a6421 100644 --- a/index.js +++ b/index.js @@ -34,6 +34,30 @@ function callHookFn (hookFn, namespace, name, baseDir) { let sendModulesToLoader +/** + * Creates a message channel with a port that can be used to add hooks to the + * list of exclusively included modules. + * + * This can be used to only wrap modules that are Hook'ed, however modules need + * to be hooked before they are imported. + * + * ```ts + * import { register } from 'module' + * import { Hook, createAddHookMessageChannel } from 'import-in-the-middle' + * + * const { registerOptions, waitForAllMessagesAcknowledged } = createAddHookMessageChannel() + * + * register('import-in-the-middle/hook.mjs', import.meta.url, registerOptions) + * + * Hook(['fs'], (exported, name, baseDir) => { + * // Instrument the fs module + * }) + * + * // Ensure that the loader has acknowledged all the modules + * // before we allow execution to continue + * await waitForAllMessagesAcknowledged() + * ``` + */ function createAddHookMessageChannel () { const { port1, port2 } = new MessageChannel() let pendingAckCount = 0 From 2d3fc58323ff767c5bdad3043c20f52e378aa759 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 29 Jul 2024 11:34:03 +0200 Subject: [PATCH 17/18] Mark this feature as experimental --- README.md | 10 +++++++--- index.d.ts | 4 ++++ index.js | 4 ++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 84022ed..7a16120 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,12 @@ node --import=./my-loader.mjs ./my-code.mjs ``` When registering the loader hook programmatically, it's possible to pass a list -of modules, file URLs or regular expressions to either exclude or specifically -include which modules are intercepted. This is useful if a module is not +of modules, file URLs or regular expressions to either `exclude` or specifically +`include` which modules are intercepted. This is useful if a module is not compatible with the loader hook. + +> **Note:** This feature is incompatible with the `{internals: true}` Hook option + ```js import * as module from 'module' @@ -71,7 +74,8 @@ module.register('import-in-the-middle/hook.mjs', import.meta.url, { }) ``` -### Only Intercepting Hooked modules +### Only Intercepting Hooked modules +> **Note:** This feature is experimental and is incompatible with the `{internals: true}` Hook option If you are `Hook`'ing all modules before they are imported, for example in a module loaded via the Node.js `--import` CLI argument, you can configure the diff --git a/index.d.ts b/index.d.ts index 683a63a..0faeb54 100644 --- a/index.d.ts +++ b/index.d.ts @@ -92,6 +92,10 @@ type CreateAddHookMessageChannelReturn = { } /** + * EXPERIMENTAL + * This feature is experimental and may change in minor versions. + * **NOTE** This feature is incompatible with the {internals: true} Hook option. + * * Creates a message channel with a port that can be used to add hooks to the * list of exclusively included modules. * diff --git a/index.js b/index.js index 58a6421..0514c95 100644 --- a/index.js +++ b/index.js @@ -35,6 +35,10 @@ function callHookFn (hookFn, namespace, name, baseDir) { let sendModulesToLoader /** + * EXPERIMENTAL + * This feature is experimental and may change in minor versions. + * **NOTE** This feature is incompatible with the {internals: true} Hook option. + * * Creates a message channel with a port that can be used to add hooks to the * list of exclusively included modules. * From e0ebcb9dc9f535dcfe32a7fad93e816a7392e0b2 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 29 Jul 2024 19:59:07 +0200 Subject: [PATCH 18/18] Fix up PR merges --- hook.js | 9 ++++++++- test/fixtures/import-after.mjs | 5 ++++- test/fixtures/import.mjs | 6 ++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/hook.js b/hook.js index e71e9dc..6f0adbd 100644 --- a/hook.js +++ b/hook.js @@ -288,7 +288,14 @@ function createHook (meta) { includeModules = [] } - includeModules.push(...modules) + for (const each of modules) { + if (!each.startsWith('node:') && builtinModules.includes(each)) { + includeModules.push(`node:${each}`) + } + + includeModules.push(each) + } + data.addHookMessagePort.postMessage('ack') }).unref() } diff --git a/test/fixtures/import-after.mjs b/test/fixtures/import-after.mjs index 1c1b0fd..1b7cbbb 100644 --- a/test/fixtures/import-after.mjs +++ b/test/fixtures/import-after.mjs @@ -1,5 +1,6 @@ import { strictEqual } from 'assert' import { sep } from 'path' +import * as os from 'node:os' import { Hook } from '../../index.js' const hooked = [] @@ -8,6 +9,8 @@ Hook((_, name) => { hooked.push(name) }) -strictEqual(hooked.length, 1) +strictEqual(hooked.length, 2) strictEqual(hooked[0], 'path') +strictEqual(hooked[1], 'os') strictEqual(sep, '@') +strictEqual(os.arch(), 'new_crazy_arch') diff --git a/test/fixtures/import.mjs b/test/fixtures/import.mjs index 96f109d..08914cf 100644 --- a/test/fixtures/import.mjs +++ b/test/fixtures/import.mjs @@ -12,6 +12,12 @@ Hook(['path'], (exports) => { exports.sep = '@' }) +Hook(['os'], (exports) => { + exports.arch = function () { + return 'new_crazy_arch' + } +}) + console.assert(path.sep !== '@') await waitForAllMessagesAcknowledged()