Skip to content

Commit a66fbbd

Browse files
committed
module: preserve URL in the parent created by createRequire()
Previously, createRequire() does not preserve the URL it gets passed in the mock parent module created, which can be observable if it's used together with module.registerHooks(). This patch adds preservation of the URL if createRequire() is invoked with one.
1 parent 85852a3 commit a66fbbd

File tree

4 files changed

+50
-13
lines changed

4 files changed

+50
-13
lines changed

lib/internal/modules/cjs/loader.js

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ const { BuiltinModule } = require('internal/bootstrap/realm');
135135
const {
136136
maybeCacheSourceMap,
137137
} = require('internal/source_map/source_map_cache');
138-
const { pathToFileURL, fileURLToPath, isURL } = require('internal/url');
138+
const { pathToFileURL, fileURLToPath, isURL, URL } = require('internal/url');
139139
const {
140140
pendingDeprecate,
141141
emitExperimentalWarning,
@@ -1923,7 +1923,7 @@ Module._extensions['.node'] = function(module, filename) {
19231923
* @param {string} filename The path to the module
19241924
* @returns {any}
19251925
*/
1926-
function createRequireFromPath(filename) {
1926+
function createRequireFromPath(filename, fileURL) {
19271927
// Allow a directory to be passed as the filename
19281928
const trailingSlash =
19291929
StringPrototypeEndsWith(filename, '/') ||
@@ -1935,6 +1935,10 @@ function createRequireFromPath(filename) {
19351935

19361936
const m = new Module(proxyPath);
19371937
m.filename = proxyPath;
1938+
if (fileURL !== undefined) {
1939+
// Save the URL if createRequire() was given a URL, to preserve search params, if any.
1940+
m[kURL] = fileURL.href;
1941+
}
19381942

19391943
m.paths = Module._nodeModulePaths(m.path);
19401944
return makeRequireFunction(m, null);
@@ -1945,28 +1949,32 @@ const createRequireError = 'must be a file URL object, file URL string, or ' +
19451949

19461950
/**
19471951
* Creates a new `require` function that can be used to load modules.
1948-
* @param {string | URL} filename The path or URL to the module context for this `require`
1952+
* @param {string | URL} filenameOrURL The path or URL to the module context for this `require`
19491953
* @throws {ERR_INVALID_ARG_VALUE} If `filename` is not a string or URL, or if it is a relative path that cannot be
19501954
* resolved to an absolute path.
19511955
* @returns {object}
19521956
*/
1953-
function createRequire(filename) {
1954-
let filepath;
1957+
function createRequire(filenameOrURL) {
1958+
let filepath, fileURL;
19551959

1956-
if (isURL(filename) ||
1957-
(typeof filename === 'string' && !path.isAbsolute(filename))) {
1960+
if (isURL(filenameOrURL) ||
1961+
(typeof filenameOrURL === 'string' && !path.isAbsolute(filenameOrURL))) {
19581962
try {
1959-
filepath = fileURLToPath(filename);
1963+
// It might be an URL, try to convert it.
1964+
// If it's a relative path, it would not parse and would be considered invalid per
1965+
// the documented contract.
1966+
fileURL = new URL(filenameOrURL);
1967+
filepath = fileURLToPath(fileURL);
19601968
} catch {
1961-
throw new ERR_INVALID_ARG_VALUE('filename', filename,
1969+
throw new ERR_INVALID_ARG_VALUE('filename', filenameOrURL,
19621970
createRequireError);
19631971
}
1964-
} else if (typeof filename !== 'string') {
1965-
throw new ERR_INVALID_ARG_VALUE('filename', filename, createRequireError);
1972+
} else if (typeof filenameOrURL !== 'string') {
1973+
throw new ERR_INVALID_ARG_VALUE('filename', filenameOrURL, createRequireError);
19661974
} else {
1967-
filepath = filename;
1975+
filepath = filenameOrURL;
19681976
}
1969-
return createRequireFromPath(filepath);
1977+
return createRequireFromPath(filepath, fileURL);
19701978
}
19711979

19721980
/**
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { createRequire } from 'node:module'
2+
const require = createRequire(import.meta.url);
3+
require('./empty.mjs');

test/fixtures/module-hooks/empty.mjs

Whitespace-only changes.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Verify that if URL is used to createRequire, that URL is passed to the resolve hook
2+
// as parentURL.
3+
import * as common from '../common/index.mjs';
4+
import assert from 'node:assert';
5+
import { registerHooks } from 'node:module';
6+
import * as fixtures from '../common/fixtures.mjs';
7+
8+
const fixtureURL = fixtures.fileURL('module-hooks/create-require-with-url.mjs').href + '?test=1';
9+
registerHooks({
10+
resolve: common.mustCall((specifier, context, defaultResolve) => {
11+
const resolved = defaultResolve(specifier, context, defaultResolve);
12+
if (specifier.startsWith('node:')) {
13+
return resolved;
14+
}
15+
16+
if (specifier === fixtureURL) {
17+
assert.strictEqual(context.parentURL, import.meta.url);
18+
} else { // From the createRequire call.
19+
assert.strictEqual(specifier, './empty.mjs');
20+
assert.strictEqual(context.parentURL, fixtureURL);
21+
}
22+
return resolved;
23+
}, 3),
24+
});
25+
26+
await import(fixtureURL);

0 commit comments

Comments
 (0)