Commit d699704f717 for nodejs
commit d699704f717c464eab9431880cac8f6aaf5da207
Author: Geoffrey Booth <webadmin@geoffreybooth.com>
Date: Tue Oct 27 21:31:45 2026 -0700
test: use registerHooks() over module.register()
Port the tests that registered async module customization hooks, via
`--loader`, `--experimental-loader` or `module.register()`, only as a
means of exercising other features to use `module.registerHooks()`
instead. This keeps the coverage for those features independent of the
async module customization hooks, which are being removed.
Tests that exist only to exercise the async module customization hooks
themselves are left as they are.
Assisted-by: Claude
Signed-off-by: Geoffrey Booth <webadmin@geoffreybooth.com>
PR-URL: https://github.com/nodejs/node/pull/67074
Refs: https://github.com/nodejs/node/pull/62395
Refs: https://github.com/nodejs/node/pull/62401
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
diff --git a/test/es-module/test-esm-assertionless-json-import.js b/test/es-module/test-esm-assertionless-json-import.js
index f5dc5ff8105..70ae4108845 100644
--- a/test/es-module/test-esm-assertionless-json-import.js
+++ b/test/es-module/test-esm-assertionless-json-import.js
@@ -1,4 +1,4 @@
-// Flags: --experimental-loader ./test/fixtures/es-module-loaders/assertionless-json-import.mjs
+// Flags: --import ./test/fixtures/es-module-loaders/assertionless-json-import.mjs
'use strict';
const common = require('../common');
const assert = require('assert');
diff --git a/test/es-module/test-esm-detect-ambiguous.mjs b/test/es-module/test-esm-detect-ambiguous.mjs
index 170b1569382..c9c739a9e23 100644
--- a/test/es-module/test-esm-detect-ambiguous.mjs
+++ b/test/es-module/test-esm-detect-ambiguous.mjs
@@ -147,18 +147,20 @@ describe('Module syntax detection', { concurrency: !process.env.TEST_PARALLEL },
}
it('should not hint wrong format in resolve hook', async () => {
- // eslint-disable-next-line no-unassigned-vars
- let writeSync;
const { stdout, stderr, code, signal } = await spawnPromisified(process.execPath, [
'--no-warnings',
- '--loader',
- `data:text/javascript,import { writeSync } from "node:fs"; export ${encodeURIComponent(
- async function resolve(s, c, next) {
- const result = await next(s, c);
- writeSync(1, result.format + '\n');
- return result;
- }
- )}`,
+ '--import',
+ 'data:text/javascript,' + encodeURIComponent(`
+ import { writeSync } from 'node:fs';
+ import { registerHooks } from 'node:module';
+ registerHooks({
+ resolve(specifier, context, nextResolve) {
+ const result = nextResolve(specifier, context);
+ writeSync(1, result.format + '\\n');
+ return result;
+ },
+ });
+ `),
fixtures.path('es-modules/package-without-type/noext-esm'),
]);
@@ -166,7 +168,6 @@ describe('Module syntax detection', { concurrency: !process.env.TEST_PARALLEL },
assert.strictEqual(stdout, 'null\nexecuted\n');
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
-
});
});
diff --git a/test/es-module/test-esm-import-meta-resolve.mjs b/test/es-module/test-esm-import-meta-resolve.mjs
index 979881e1923..ba43b361c58 100644
--- a/test/es-module/test-esm-import-meta-resolve.mjs
+++ b/test/es-module/test-esm-import-meta-resolve.mjs
@@ -94,7 +94,10 @@ await assert.rejects(import('data:text/javascript,export default import.meta.res
const result = await spawnPromisified(execPath, [
'--no-warnings',
'--input-type=module',
- '--import', 'data:text/javascript,import{register}from"node:module";register("data:text/javascript,")',
+ '--import', 'data:text/javascript,' + encodeURIComponent(
+ 'import { registerHooks } from "node:module";' +
+ 'registerHooks({ resolve: (s, c, next) => next(s, c) });',
+ ),
'--eval',
'console.log(import.meta.resolve(new URL("http://example.com")))',
]);
diff --git a/test/es-module/test-esm-virtual-json.mjs b/test/es-module/test-esm-virtual-json.mjs
index a42b037fc1f..d7bf4ffc122 100644
--- a/test/es-module/test-esm-virtual-json.mjs
+++ b/test/es-module/test-esm-virtual-json.mjs
@@ -1,10 +1,10 @@
import '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
-import { register } from 'node:module';
+import { registerHooks } from 'node:module';
import assert from 'node:assert';
-async function resolve(referrer, context, next) {
- const result = await next(referrer, context);
+function resolve(referrer, context, next) {
+ const result = next(referrer, context);
const url = new URL(result.url);
url.searchParams.set('randomSeed', Math.random());
result.url = url.href;
@@ -22,7 +22,7 @@ function load(url, context, next) {
return next(url, context);
}
-register(`data:text/javascript,export ${encodeURIComponent(resolve)};export ${encodeURIComponent(load)}`);
+registerHooks({ resolve, load });
assert.notDeepStrictEqual(
await import(fixtures.fileURL('empty.json'), { with: { type: 'json' } }),
diff --git a/test/fixtures/es-module-loaders/assertionless-json-import.mjs b/test/fixtures/es-module-loaders/assertionless-json-import.mjs
index 3ffc4c1148f..a83c1545dc3 100644
--- a/test/fixtures/es-module-loaders/assertionless-json-import.mjs
+++ b/test/fixtures/es-module-loaders/assertionless-json-import.mjs
@@ -1,15 +1,22 @@
+import { registerHooks } from 'node:module';
+
const DATA_URL_PATTERN = /^data:application\/json(?:[^,]*?)(;base64)?,([\s\S]*)$/;
const JSON_URL_PATTERN = /^[^?]+\.json(\?[^#]*)?(#.*)?$/;
-export async function resolve(specifier, context, next) {
- const noAttributesSpecified = context.importAttributes.type == null;
+function resolve(specifier, context, next) {
+ // Hooks also run for require() calls, which have no import attributes.
+ if (context.importAttributes === undefined) {
+ return next(specifier, context);
+ }
- // Mutation from resolve hook should be discarded.
- context.importAttributes.type = 'whatever';
+ const noAttributesSpecified = context.importAttributes.type == null;
// This fixture assumes that no other resolve hooks in the chain will error on invalid import attributes
// (as defaultResolve doesn't).
- const result = await next(specifier, context);
+ const result = next(specifier, {
+ ...context,
+ importAttributes: { ...context.importAttributes, type: 'whatever' },
+ });
if (noAttributesSpecified &&
(DATA_URL_PATTERN.test(result.url) || JSON_URL_PATTERN.test(result.url))) {
@@ -22,3 +29,5 @@ export async function resolve(specifier, context, next) {
return result;
}
+
+registerHooks({ resolve });
diff --git a/test/fixtures/es-module-shadow-realm/custom-loaders.js b/test/fixtures/es-module-shadow-realm/custom-hooks.js
similarity index 69%
rename from test/fixtures/es-module-shadow-realm/custom-loaders.js
rename to test/fixtures/es-module-shadow-realm/custom-hooks.js
index bf4402250cc..b62d25fb62f 100644
--- a/test/fixtures/es-module-shadow-realm/custom-loaders.js
+++ b/test/fixtures/es-module-shadow-realm/custom-hooks.js
@@ -1,10 +1,10 @@
-// This fixture is used to test that custom loaders are not enabled in the ShadowRealm.
+// This fixture is used to test that customization hooks are not enabled in the ShadowRealm.
'use strict';
const assert = require('assert');
async function workInChildProcess() {
- // Assert that the process is running with a custom loader.
+ // Assert that the process is running with custom hooks.
const moduleNamespace = await import('file:///42.mjs');
assert.strictEqual(moduleNamespace.default, 42);
diff --git a/test/fixtures/es-module-shadow-realm/register-hooks.js b/test/fixtures/es-module-shadow-realm/register-hooks.js
new file mode 100644
index 00000000000..46b51a3a842
--- /dev/null
+++ b/test/fixtures/es-module-shadow-realm/register-hooks.js
@@ -0,0 +1,17 @@
+'use strict';
+const { registerHooks } = require('node:module');
+
+registerHooks({
+ resolve(specifier, context, nextResolve) {
+ if (specifier.startsWith('file:///') && specifier.includes('42')) {
+ return { shortCircuit: true, url: specifier };
+ }
+ return nextResolve(specifier, context);
+ },
+ load(url, context, nextLoad) {
+ if (url.startsWith('file:///') && url.includes('42')) {
+ return { shortCircuit: true, format: 'module', source: 'export default 42' };
+ }
+ return nextLoad(url, context);
+ },
+});
diff --git a/test/fixtures/module-hooks/register-logger-async-hooks.mjs b/test/fixtures/module-hooks/register-logger-async-hooks.mjs
deleted file mode 100644
index 28a6ae5f223..00000000000
--- a/test/fixtures/module-hooks/register-logger-async-hooks.mjs
+++ /dev/null
@@ -1,2 +0,0 @@
-import { register } from 'node:module';
-register('./logger-async-hooks.mjs', import.meta.url);
diff --git a/test/fixtures/module-hooks/register-logger-hooks.mjs b/test/fixtures/module-hooks/register-logger-hooks.mjs
new file mode 100644
index 00000000000..f6582b7525b
--- /dev/null
+++ b/test/fixtures/module-hooks/register-logger-hooks.mjs
@@ -0,0 +1,13 @@
+import { registerHooks } from 'node:module';
+import { writeSync } from 'node:fs';
+
+registerHooks({
+ resolve(specifier, context, nextResolve) {
+ writeSync(1, `resolve ${specifier} from ${context.parentURL}\n`);
+ return nextResolve(specifier, context);
+ },
+ load(url, context, nextLoad) {
+ writeSync(1, `load ${url}\n`);
+ return nextLoad(url, context);
+ },
+});
diff --git a/test/fixtures/test-runner/coverage-loader/hooks.mjs b/test/fixtures/test-runner/coverage-loader/hooks.mjs
index 57ee712ed97..2326d18406c 100644
--- a/test/fixtures/test-runner/coverage-loader/hooks.mjs
+++ b/test/fixtures/test-runner/coverage-loader/hooks.mjs
@@ -1,3 +1,5 @@
+import { registerHooks } from 'node:module';
+
const sources = {
// Virtual file. Doesn't exist on disk
"virtual.js": `
@@ -16,10 +18,12 @@ import{describe,it}from"node:test";import assert from"node:assert";import{sum}fr
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6ImtIQUFPLFNBQVMsT0FBUSxFQUFHLENBQ3pCLEdBQUksQ0FBQyxFQUFFLE1BQU8sS0FBUSxPQUFPLE1BQVEsUUFBUSxFQUFHLE1BQU0sSUFBSSxNQUFNLGNBQWMsRUFDOUUsT0FBTyxFQUFFLE9BQU8sQ0FBQyxJQUFLLE1BQVEsSUFBTSxHQUFHLENBQ3pDLENBSGdCIiwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbXSwic291cmNlcyI6WyIuL3N1bS50cyJdLCJzb3VyY2VzQ29udGVudCI6W251bGxdfQ==`,
};
-export async function load(url, context, nextLoad) {
+function load(url, context, nextLoad) {
const file = url.split('/').at(-1);
if (sources[file] !== undefined) {
return { format: "module", source: sources[file], shortCircuit: true };
}
return nextLoad(url, context);
}
+
+registerHooks({ load });
diff --git a/test/fixtures/test-runner/coverage-loader/register-hooks.js b/test/fixtures/test-runner/coverage-loader/register-hooks.js
deleted file mode 100644
index cbbac612641..00000000000
--- a/test/fixtures/test-runner/coverage-loader/register-hooks.js
+++ /dev/null
@@ -1,4 +0,0 @@
-const { register } = require('node:module');
-const { pathToFileURL } = require('node:url');
-
-register('./hooks.mjs', pathToFileURL(__filename));
diff --git a/test/fixtures/typescript/ts/hook.ts b/test/fixtures/typescript/ts/hook.ts
deleted file mode 100644
index e0dd46448b8..00000000000
--- a/test/fixtures/typescript/ts/hook.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import type { ResolveHook } from 'node:module';
-
-// Pass through
-export const resolve: ResolveHook = async function resolve(specifier, context, nextResolve) {
- if(false){
- // https://github.com/nodejs/node/issues/54645
- // A bug in the typescript parsers swc causes
- // the next line to not be parsed correctly
- }
- return nextResolve(specifier, context);
-};
diff --git a/test/fixtures/typescript/ts/test-loader.ts b/test/fixtures/typescript/ts/test-loader.ts
index 8b957bf72f0..33b4a11ef26 100644
--- a/test/fixtures/typescript/ts/test-loader.ts
+++ b/test/fixtures/typescript/ts/test-loader.ts
@@ -1,4 +1,9 @@
-import { register } from 'node:module';
-import * as fixtures from '../../../common/fixtures.mjs';
+import { registerHooks } from 'node:module';
+import type { ResolveHookSync } from 'node:module';
-register(fixtures.fileURL('typescript/ts/hook.ts'));
+// Pass through
+const resolve: ResolveHookSync = function resolve(specifier, context, nextResolve) {
+ return nextResolve(specifier, context);
+};
+
+registerHooks({ resolve });
diff --git a/test/module-hooks/test-module-hooks-require-esm.js b/test/module-hooks/test-module-hooks-require-esm.js
index a3bb0dd2c4b..9bfe3e5e674 100644
--- a/test/module-hooks/test-module-hooks-require-esm.js
+++ b/test/module-hooks/test-module-hooks-require-esm.js
@@ -1,18 +1,15 @@
'use strict';
-// This tests that the async loader hooks can be invoked for require(esm).
+// This tests that module.registerHooks() hooks are invoked for require(esm)
+// and for the CommonJS and ES module graphs reachable from it.
-require('../common');
const common = require('../common');
const { spawnSyncAndAssert } = require('../common/child_process');
const fixtures = require('../common/fixtures');
const assert = require('assert');
function assertSubGraph(output) {
- // FIXME(node:59666): the async resolve hook invoked for require() in imported CJS only get the URL,
- // not the specifier. This may be fixable if we re-implement the async loader hooks from within
- // the synchronous loader hooks and use the original require() implementation.
- assert.match(output, /resolve .+esm\.mjs from file:.+main\.cjs/);
+ assert.match(output, /resolve \.\/esm\.mjs from file:.+main\.cjs/);
assert.match(output, /load file:.+esm\.mjs/);
assert.match(output, /resolve \.\/inner\.mjs from file:.+esm\.mjs/);
@@ -21,8 +18,7 @@ function assertSubGraph(output) {
assert.match(output, /resolve \.\/cjs\.cjs from file:.+esm\.mjs/);
assert.match(output, /load file:.+cjs\.cjs/);
- // FIXME(node:59666): see above.
- assert.match(output, /resolve .+inner\.cjs from file:.+cjs\.cjs/);
+ assert.match(output, /resolve \.\/inner\.cjs from file:.+cjs\.cjs/);
assert.match(output, /load file:.+inner\.cjs/);
assert.match(output, /esmValue in main\.cjs: esm/);
@@ -31,7 +27,7 @@ function assertSubGraph(output) {
spawnSyncAndAssert(process.execPath, [
'--import',
- fixtures.fileURL('module-hooks', 'register-logger-async-hooks.mjs'),
+ fixtures.fileURL('module-hooks', 'register-logger-hooks.mjs'),
fixtures.path('module-hooks', 'require-esm', 'main.cjs'),
], {
stdout: common.mustCall((output) => {
@@ -50,7 +46,7 @@ spawnSyncAndAssert(process.execPath, [
spawnSyncAndAssert(process.execPath, [
'--import',
- fixtures.fileURL('module-hooks', 'register-logger-async-hooks.mjs'),
+ fixtures.fileURL('module-hooks', 'register-logger-hooks.mjs'),
fixtures.path('module-hooks', 'require-esm', 'main.mjs'),
], {
stdout: common.mustCall((output) => {
@@ -64,7 +60,7 @@ spawnSyncAndAssert(process.execPath, [
assert.match(output, /resolve .+main\.mjs from undefined/);
assert.match(output, /load file:.+main\.mjs/);
- assert.match(output, /resolve .+main\.cjs from file:.+main\.mjs/);
+ assert.match(output, /resolve \.\/main\.cjs from file:.+main\.mjs/);
assert.match(output, /load file:.+main\.cjs/);
assertSubGraph(output);
diff --git a/test/parallel/test-find-package-json.js b/test/parallel/test-find-package-json.js
index fd9a1a85e77..d1df637dcc5 100644
--- a/test/parallel/test-find-package-json.js
+++ b/test/parallel/test-find-package-json.js
@@ -150,19 +150,23 @@ describe('findPackageJSON', () => { // Throws when no arguments are provided
}));
});
- it('should work within a loader', async () => {
+ it('should work within a resolve hook', async () => {
const specifierBase = './packages/root-types-field';
const target = fixtures.fileURL(specifierBase, 'index.js');
const foundPjsonPath = path.toNamespacedPath(fixtures.path(specifierBase, 'package.json'));
const { code, stderr, stdout } = await common.spawnPromisified(process.execPath, [
'--no-warnings',
- '--loader',
+ '--import',
[
'data:text/javascript,',
'import fs from "node:fs";',
'import module from "node:module";',
- encodeURIComponent(`fs.writeSync(1, module.findPackageJSON(${JSON.stringify(target)}));`),
- 'export const resolve = async (s, c, n) => n(s);',
+ encodeURIComponent(`module.registerHooks({
+ resolve(specifier, context, nextResolve) {
+ fs.writeSync(1, module.findPackageJSON(${JSON.stringify(target)}));
+ return nextResolve(specifier, context);
+ },
+ });`),
].join(''),
'--eval',
'import "node:os";', // Can be anything that triggers the resolve hook chain
@@ -173,23 +177,6 @@ describe('findPackageJSON', () => { // Throws when no arguments are provided
assert.strictEqual(code, 0);
});
- it('should work with an async resolve hook registered', async () => {
- const specifierBase = './packages/root-types-field';
- const target = fixtures.fileURL(specifierBase, 'index.js');
- const foundPjsonPath = path.toNamespacedPath(fixtures.path(specifierBase, 'package.json'));
- const { code, stderr, stdout } = await common.spawnPromisified(process.execPath, [
- '--no-warnings',
- '--loader',
- 'data:text/javascript,export const resolve = async (s, c, n) => n(s);',
- '--print',
- `require("node:module").findPackageJSON(${JSON.stringify(target)})`,
- ]);
-
- assert.strictEqual(stderr, '');
- assert.ok(stdout.includes(foundPjsonPath), stdout);
- assert.strictEqual(code, 0);
- });
-
it('should work when unrecognised keys are specified within the export', async () => {
const specifierBase = './packages/unrecognised-export-keys';
const target = fixtures.fileURL(specifierBase, 'index.js');
diff --git a/test/parallel/test-runner-cli.js b/test/parallel/test-runner-cli.js
index 28ad56a0a93..000c9b8be89 100644
--- a/test/parallel/test-runner-cli.js
+++ b/test/parallel/test-runner-cli.js
@@ -283,10 +283,10 @@ for (const isolation of ['none', 'process']) {
}
{
- // Use test with --loader and --require.
+ // Use test with --import and --require.
// This case is common since vscode uses --require to load the debugger.
const args = ['--no-warnings',
- '--experimental-loader', 'data:text/javascript,',
+ '--import', 'data:text/javascript,',
'--require', fixtures.path('empty.js'),
'--test', join(testFixtures, 'default-behavior', 'index.test.js')];
const child = spawnSync(process.execPath, args);
diff --git a/test/parallel/test-runner-coverage.js b/test/parallel/test-runner-coverage.js
index fa56c5af3e8..020adfb825a 100644
--- a/test/parallel/test-runner-coverage.js
+++ b/test/parallel/test-runner-coverage.js
@@ -357,15 +357,14 @@ test('coverage reports on lines, functions, and branches', skipIfNoInspector, as
test('coverage with ESM hook - source irrelevant', skipIfNoInspector, () => {
let report = [
'# start of coverage report',
- '# ------------------------------------------------------------------',
- '# file | line % | branch % | funcs % | uncovered lines',
- '# ------------------------------------------------------------------',
- '# hooks.mjs | 100.00 | 100.00 | 100.00 | ',
- '# register-hooks.js | 100.00 | 100.00 | 100.00 | ',
- '# virtual.js | 100.00 | 100.00 | 100.00 | ',
- '# ------------------------------------------------------------------',
- '# all files | 100.00 | 100.00 | 100.00 | ',
- '# ------------------------------------------------------------------',
+ '# -----------------------------------------------------------',
+ '# file | line % | branch % | funcs % | uncovered lines',
+ '# -----------------------------------------------------------',
+ '# hooks.mjs | 100.00 | 100.00 | 100.00 | ',
+ '# virtual.js | 100.00 | 100.00 | 100.00 | ',
+ '# -----------------------------------------------------------',
+ '# all files | 100.00 | 100.00 | 100.00 | ',
+ '# -----------------------------------------------------------',
'# end of coverage report',
].join('\n');
@@ -376,7 +375,7 @@ test('coverage with ESM hook - source irrelevant', skipIfNoInspector, () => {
const fixture = fixtures.path('test-runner', 'coverage-loader');
const args = [
'--import',
- './register-hooks.js',
+ './hooks.mjs',
'--test',
'--experimental-test-coverage',
'--test-coverage-exclude=!test/**',
@@ -394,16 +393,15 @@ test('coverage with ESM hook - source irrelevant', skipIfNoInspector, () => {
test('coverage with ESM hook - source transpiled', skipIfNoInspector, () => {
let report = [
'# start of coverage report',
- '# ------------------------------------------------------------------',
- '# file | line % | branch % | funcs % | uncovered lines',
- '# ------------------------------------------------------------------',
- '# hooks.mjs | 100.00 | 100.00 | 100.00 | ',
- '# register-hooks.js | 100.00 | 100.00 | 100.00 | ',
- '# sum.test.ts | 100.00 | 100.00 | 100.00 | ',
- '# sum.ts | 100.00 | 100.00 | 100.00 | ',
- '# ------------------------------------------------------------------',
- '# all files | 100.00 | 100.00 | 100.00 | ',
- '# ------------------------------------------------------------------',
+ '# ------------------------------------------------------------',
+ '# file | line % | branch % | funcs % | uncovered lines',
+ '# ------------------------------------------------------------',
+ '# hooks.mjs | 100.00 | 100.00 | 100.00 | ',
+ '# sum.test.ts | 100.00 | 100.00 | 100.00 | ',
+ '# sum.ts | 100.00 | 100.00 | 100.00 | ',
+ '# ------------------------------------------------------------',
+ '# all files | 100.00 | 100.00 | 100.00 | ',
+ '# ------------------------------------------------------------',
'# end of coverage report',
].join('\n');
@@ -413,7 +411,7 @@ test('coverage with ESM hook - source transpiled', skipIfNoInspector, () => {
const fixture = fixtures.path('test-runner', 'coverage-loader');
const args = [
- '--import', './register-hooks.js',
+ '--import', './hooks.mjs',
'--test',
'--experimental-test-coverage',
'--test-coverage-exclude=!test/**',
diff --git a/test/parallel/test-shadow-realm-custom-loaders.js b/test/parallel/test-shadow-realm-custom-loaders.js
index 80cda74bb88..311a8af9b96 100644
--- a/test/parallel/test-shadow-realm-custom-loaders.js
+++ b/test/parallel/test-shadow-realm-custom-loaders.js
@@ -9,14 +9,12 @@ const commonArgs = [
];
async function main() {
- // Verifies that custom loaders are not enabled in the ShadowRealm.
+ // Verifies that customization hooks are not enabled in the ShadowRealm.
const child = await common.spawnPromisified(process.execPath, [
...commonArgs,
- '--experimental-loader',
- fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'),
- '--experimental-loader',
- fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
- fixtures.path('es-module-shadow-realm', 'custom-loaders.js'),
+ '--require',
+ fixtures.path('es-module-shadow-realm', 'register-hooks.js'),
+ fixtures.path('es-module-shadow-realm', 'custom-hooks.js'),
]);
assert.strictEqual(child.stderr, '');