diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 6409b913e78602..2f8a5d8c9ce8ac 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -135,7 +135,7 @@ const { BuiltinModule } = require('internal/bootstrap/realm'); const { maybeCacheSourceMap, } = require('internal/source_map/source_map_cache'); -const { pathToFileURL, fileURLToPath, isURL } = require('internal/url'); +const { pathToFileURL, fileURLToPath, isURL, URL } = require('internal/url'); const { pendingDeprecate, emitExperimentalWarning, @@ -1923,7 +1923,7 @@ Module._extensions['.node'] = function(module, filename) { * @param {string} filename The path to the module * @returns {any} */ -function createRequireFromPath(filename) { +function createRequireFromPath(filename, fileURL) { // Allow a directory to be passed as the filename const trailingSlash = StringPrototypeEndsWith(filename, '/') || @@ -1935,6 +1935,10 @@ function createRequireFromPath(filename) { const m = new Module(proxyPath); m.filename = proxyPath; + if (fileURL !== undefined) { + // Save the URL if createRequire() was given a URL, to preserve search params, if any. + m[kURL] = fileURL.href; + } m.paths = Module._nodeModulePaths(m.path); return makeRequireFunction(m, null); @@ -1945,28 +1949,32 @@ const createRequireError = 'must be a file URL object, file URL string, or ' + /** * Creates a new `require` function that can be used to load modules. - * @param {string | URL} filename The path or URL to the module context for this `require` + * @param {string | URL} filenameOrURL The path or URL to the module context for this `require` * @throws {ERR_INVALID_ARG_VALUE} If `filename` is not a string or URL, or if it is a relative path that cannot be * resolved to an absolute path. * @returns {object} */ -function createRequire(filename) { - let filepath; +function createRequire(filenameOrURL) { + let filepath, fileURL; - if (isURL(filename) || - (typeof filename === 'string' && !path.isAbsolute(filename))) { + if (isURL(filenameOrURL) || + (typeof filenameOrURL === 'string' && !path.isAbsolute(filenameOrURL))) { try { - filepath = fileURLToPath(filename); + // It might be an URL, try to convert it. + // If it's a relative path, it would not parse and would be considered invalid per + // the documented contract. + fileURL = new URL(filenameOrURL); + filepath = fileURLToPath(fileURL); } catch { - throw new ERR_INVALID_ARG_VALUE('filename', filename, + throw new ERR_INVALID_ARG_VALUE('filename', filenameOrURL, createRequireError); } - } else if (typeof filename !== 'string') { - throw new ERR_INVALID_ARG_VALUE('filename', filename, createRequireError); + } else if (typeof filenameOrURL !== 'string') { + throw new ERR_INVALID_ARG_VALUE('filename', filenameOrURL, createRequireError); } else { - filepath = filename; + filepath = filenameOrURL; } - return createRequireFromPath(filepath); + return createRequireFromPath(filepath, fileURL); } /** diff --git a/test/fixtures/module-hooks/create-require-with-url.mjs b/test/fixtures/module-hooks/create-require-with-url.mjs new file mode 100644 index 00000000000000..4773185be6e5da --- /dev/null +++ b/test/fixtures/module-hooks/create-require-with-url.mjs @@ -0,0 +1,3 @@ +import { createRequire } from 'node:module' +const require = createRequire(import.meta.url); +require('./empty.mjs'); diff --git a/test/fixtures/module-hooks/empty.mjs b/test/fixtures/module-hooks/empty.mjs new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/test/module-hooks/test-module-hooks-create-require-with-url.mjs b/test/module-hooks/test-module-hooks-create-require-with-url.mjs new file mode 100644 index 00000000000000..4b095a4cea849c --- /dev/null +++ b/test/module-hooks/test-module-hooks-create-require-with-url.mjs @@ -0,0 +1,26 @@ +// Verify that if URL is used to createRequire, that URL is passed to the resolve hook +// as parentURL. +import * as common from '../common/index.mjs'; +import assert from 'node:assert'; +import { registerHooks } from 'node:module'; +import * as fixtures from '../common/fixtures.mjs'; + +const fixtureURL = fixtures.fileURL('module-hooks/create-require-with-url.mjs').href + '?test=1'; +registerHooks({ + resolve: common.mustCall((specifier, context, defaultResolve) => { + const resolved = defaultResolve(specifier, context, defaultResolve); + if (specifier.startsWith('node:')) { + return resolved; + } + + if (specifier === fixtureURL) { + assert.strictEqual(context.parentURL, import.meta.url); + } else { // From the createRequire call. + assert.strictEqual(specifier, './empty.mjs'); + assert.strictEqual(context.parentURL, fixtureURL); + } + return resolved; + }, 3), +}); + +await import(fixtureURL);