module: fix sync resolve hooks for require with node: prefixes · nodejs/node@b9240bc · GitHub
Skip to content

Commit b9240bc

Browse files
joyeecheungmarco-ippolito
authored andcommitted
module: fix sync resolve hooks for require with node: prefixes
Previously, when require()-ing builtins with the node: prefix, the sync resolve hooks were not properly invoked, and load hooks could not override the builtin's format. This fixes the handling and enables redirecting prefixed built-ins to on-disk files and overriding them with other module types via hooks. PR-URL: #61088 Backport-PR-URL: #62029 Fixes: #60005 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com> Fixes: #61801
1 parent 2e91b28 commit b9240bc

10 files changed

Lines changed: 360 additions & 39 deletions

lib/internal/modules/cjs/loader.js

Lines changed: 83 additions & 39 deletions
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const url = import.meta.url;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict';
2+
3+
// This tests that load hooks can override the format of builtin modules
4+
// to 'commonjs' format.
5+
const common = require('../common');
6+
const assert = require('assert');
7+
const { registerHooks } = require('module');
8+
9+
// Pick a builtin that's unlikely to be loaded already - like zlib.
10+
assert(!process.moduleLoadList.includes('NativeModule zlib'));
11+
12+
const hook = registerHooks({
13+
load: common.mustCall(function load(url, context, nextLoad) {
14+
// Only intercept zlib builtin
15+
if (url === 'node:zlib') {
16+
// Return a different format to override the builtin
17+
return {
18+
source: 'exports.custom_zlib = "overridden by load hook";',
19+
format: 'commonjs',
20+
shortCircuit: true,
21+
};
22+
}
23+
return nextLoad(url, context);
24+
}, 2), // Called twice: once for 'zlib', once for 'node:zlib'
25+
});
26+
27+
// Test: Load hook overrides builtin format to commonjs
28+
const zlib = require('zlib');
29+
assert.strictEqual(zlib.custom_zlib, 'overridden by load hook');
30+
assert.strictEqual(typeof zlib.createGzip, 'undefined'); // Original zlib API should not be available
31+
32+
// Test with node: prefix
33+
const zlib2 = require('node:zlib');
34+
assert.strictEqual(zlib2.custom_zlib, 'overridden by load hook');
35+
assert.strictEqual(typeof zlib2.createGzip, 'undefined');
36+
37+
hook.deregister();
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict';
2+
3+
// This tests that load hooks can override the format of builtin modules
4+
// to 'json' format.
5+
const common = require('../common');
6+
const assert = require('assert');
7+
const { registerHooks } = require('module');
8+
9+
// Pick a builtin that's unlikely to be loaded already - like zlib.
10+
assert(!process.moduleLoadList.includes('NativeModule zlib'));
11+
12+
const hook = registerHooks({
13+
load: common.mustCall(function load(url, context, nextLoad) {
14+
// Only intercept zlib builtin
15+
if (url === 'node:zlib') {
16+
// Return JSON format to override the builtin
17+
return {
18+
source: JSON.stringify({ custom_zlib: 'JSON overridden zlib' }),
19+
format: 'json',
20+
shortCircuit: true,
21+
};
22+
}
23+
return nextLoad(url, context);
24+
}, 2), // Called twice: once for 'zlib', once for 'node:zlib'
25+
});
26+
27+
// Test: Load hook overrides builtin format to json
28+
const zlib = require('zlib');
29+
assert.strictEqual(zlib.custom_zlib, 'JSON overridden zlib');
30+
assert.strictEqual(typeof zlib.createGzip, 'undefined'); // Original zlib API should not be available
31+
32+
// Test with node: prefix
33+
const zlib2 = require('node:zlib');
34+
assert.strictEqual(zlib2.custom_zlib, 'JSON overridden zlib');
35+
assert.strictEqual(typeof zlib2.createGzip, 'undefined');
36+
37+
hook.deregister();
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict';
2+
3+
// This tests that load hooks can override the format of builtin modules
4+
// to 'module', and require() can load them.
5+
const common = require('../common');
6+
const assert = require('assert');
7+
const { registerHooks } = require('module');
8+
9+
// Pick a builtin that's unlikely to be loaded already - like zlib.
10+
assert(!process.moduleLoadList.includes('NativeModule zlib'));
11+
12+
const hook = registerHooks({
13+
load: common.mustCall(function load(url, context, nextLoad) {
14+
// Only intercept zlib builtin
15+
if (url === 'node:zlib') {
16+
// Return ES module format to override the builtin
17+
// Note: For require() to work with ESM, we need to export 'module.exports'
18+
return {
19+
source: `const exports = { custom_zlib: "ESM overridden zlib" };
20+
export default exports;
21+
export { exports as 'module.exports' };`,
22+
format: 'module',
23+
shortCircuit: true,
24+
};
25+
}
26+
return nextLoad(url, context);
27+
}, 2), // Called twice: once for 'zlib', once for 'node:zlib'
28+
});
29+
30+
// Test: Load hook overrides builtin format to module.
31+
// With the 'module.exports' export, require() should work
32+
const zlib = require('zlib');
33+
assert.strictEqual(zlib.custom_zlib, 'ESM overridden zlib');
34+
assert.strictEqual(typeof zlib.createGzip, 'undefined'); // Original zlib API should not be available
35+
36+
// Test with node: prefix
37+
const zlib2 = require('node:zlib');
38+
assert.strictEqual(zlib2.custom_zlib, 'ESM overridden zlib');
39+
assert.strictEqual(typeof zlib2.createGzip, 'undefined');
40+
41+
hook.deregister();
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
3+
// This tests that builtins can be redirected to a local file when they are prefixed
4+
// with `node:`.
5+
require('../common');
6+
7+
const assert = require('assert');
8+
const { registerHooks } = require('module');
9+
const fixtures = require('../common/fixtures');
10+
11+
// This tests that builtins can be redirected to a local file.
12+
// Pick a builtin that's unlikely to be loaded already - like zlib.
13+
assert(!process.moduleLoadList.includes('NativeModule zlib'));
14+
15+
const hook = registerHooks({
16+
resolve(specifier, context, nextLoad) {
17+
specifier = specifier.replaceAll('node:', '');
18+
return {
19+
url: fixtures.fileURL('module-hooks', `redirected-${specifier}.js`).href,
20+
shortCircuit: true,
21+
};
22+
},
23+
});
24+
25+
// Check assert, which is already loaded.
26+
// eslint-disable-next-line node-core/must-call-assert
27+
assert.strictEqual(require('node:assert').exports_for_test, 'redirected assert');
28+
// Check zlib, which is not yet loaded.
29+
assert.strictEqual(require('node:zlib').exports_for_test, 'redirected zlib');
30+
// Check fs, which is redirected to an ESM
31+
assert.strictEqual(require('node:fs').exports_for_test, 'redirected fs');
32+
33+
hook.deregister();
Lines changed: 36 additions & 0 deletions

0 commit comments

Comments
 (0)