Skip to content

Commit edec5be

Browse files
joyeecheungaduh95
authored andcommitted
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. PR-URL: #60974 Fixes: #60973 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
1 parent 49f3672 commit edec5be

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,
@@ -1924,7 +1924,7 @@ Module._extensions['.node'] = function(module, filename) {
19241924
* @param {string} filename The path to the module
19251925
* @returns {any}
19261926
*/
1927-
function createRequireFromPath(filename) {
1927+
function createRequireFromPath(filename, fileURL) {
19281928
// Allow a directory to be passed as the filename
19291929
const trailingSlash =
19301930
StringPrototypeEndsWith(filename, '/') ||
@@ -1936,6 +1936,10 @@ function createRequireFromPath(filename) {
19361936

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

19401944
m.paths = Module._nodeModulePaths(m.path);
19411945
return makeRequireFunction(m, null);
@@ -1946,28 +1950,32 @@ const createRequireError = 'must be a file URL object, file URL string, or ' +
19461950

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

1957-
if (isURL(filename) ||
1958-
(typeof filename === 'string' && !path.isAbsolute(filename))) {
1961+
if (isURL(filenameOrURL) ||
1962+
(typeof filenameOrURL === 'string' && !path.isAbsolute(filenameOrURL))) {
19591963
try {
1960-
filepath = fileURLToPath(filename);
1964+
// It might be an URL, try to convert it.
1965+
// If it's a relative path, it would not parse and would be considered invalid per
1966+
// the documented contract.
1967+
fileURL = new URL(filenameOrURL);
1968+
filepath = fileURLToPath(fileURL);
19611969
} catch {
1962-
throw new ERR_INVALID_ARG_VALUE('filename', filename,
1970+
throw new ERR_INVALID_ARG_VALUE('filename', filenameOrURL,
19631971
createRequireError);
19641972
}
1965-
} else if (typeof filename !== 'string') {
1966-
throw new ERR_INVALID_ARG_VALUE('filename', filename, createRequireError);
1973+
} else if (typeof filenameOrURL !== 'string') {
1974+
throw new ERR_INVALID_ARG_VALUE('filename', filenameOrURL, createRequireError);
19671975
} else {
1968-
filepath = filename;
1976+
filepath = filenameOrURL;
19691977
}
1970-
return createRequireFromPath(filepath);
1978+
return createRequireFromPath(filepath, fileURL);
19711979
}
19721980

19731981
/**
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)