| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent bdc1894 commit 3d160cd
15 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1034,41 +1034,63 @@ function getExportsForCircularRequire(module) { | |||
| 1034 | 1034 | return module.exports; | |
| 1035 | 1035 | } | |
| 1036 | 1036 | ||
| 1037 | + | ||
| 1037 | 1038 | /** | |
| 1038 | - * Resolve a module request for CommonJS, invoking hooks from module.registerHooks() | ||
| 1039 | - * if necessary. | ||
| 1039 | + * Wraps result of Module._resolveFilename to include additional fields for hooks. | ||
| 1040 | + * See resolveForCJSWithHooks. | ||
| 1040 | 1041 | * @param {string} specifier | |
| 1041 | 1042 | * @param {Module|undefined} parent | |
| 1042 | 1043 | * @param {boolean} isMain | |
| 1043 | - * @param {boolean} shouldSkipModuleHooks | ||
| 1044 | + * @param {ResolveFilenameOptions} options | ||
| 1044 | 1045 | * @returns {{url?: string, format?: string, parentURL?: string, filename: string}} | |
| 1045 | 1046 | */ | |
| 1046 | - function resolveForCJSWithHooks(specifier, parent, isMain, shouldSkipModuleHooks) { | ||
| 1047 | - let defaultResolvedURL; | ||
| 1048 | - let defaultResolvedFilename; | ||
| 1049 | - let format; | ||
| 1050 | - | ||
| 1051 | - function defaultResolveImpl(specifier, parent, isMain, options) { | ||
| 1052 | - // For backwards compatibility, when encountering requests starting with node:, | ||
| 1053 | - // throw ERR_UNKNOWN_BUILTIN_MODULE on failure or return the normalized ID on success | ||
| 1054 | - // without going into Module._resolveFilename. | ||
| 1055 | - let normalized; | ||
| 1056 | - if (StringPrototypeStartsWith(specifier, 'node:')) { | ||
| 1057 | - normalized = BuiltinModule.normalizeRequirableId(specifier); | ||
| 1058 | - if (!normalized) { | ||
| 1059 | - throw new ERR_UNKNOWN_BUILTIN_MODULE(specifier); | ||
| 1060 | - } | ||
| 1061 | - defaultResolvedURL = specifier; | ||
| 1062 | - format = 'builtin'; | ||
| 1063 | - return normalized; | ||
| 1047 | + function wrapResolveFilename(specifier, parent, isMain, options) { | ||
| 1048 | + const filename = Module._resolveFilename(specifier, parent, isMain, options).toString(); | ||
| 1049 | + return { __proto__: null, url: undefined, format: undefined, filename }; | ||
| 1050 | + } | ||
| 1051 | + | ||
| 1052 | + /** | ||
| 1053 | + * See resolveForCJSWithHooks. | ||
| 1054 | + * @param {string} specifier | ||
| 1055 | + * @param {Module|undefined} parent | ||
| 1056 | + * @param {boolean} isMain | ||
| 1057 | + * @param {ResolveFilenameOptions} options | ||
| 1058 | + * @returns {{url?: string, format?: string, parentURL?: string, filename: string}} | ||
| 1059 | + */ | ||
| 1060 | + function defaultResolveImplForCJSLoading(specifier, parent, isMain, options) { | ||
| 1061 | + // For backwards compatibility, when encountering requests starting with node:, | ||
| 1062 | + // throw ERR_UNKNOWN_BUILTIN_MODULE on failure or return the normalized ID on success | ||
| 1063 | + // without going into Module._resolveFilename. | ||
| 1064 | + let normalized; | ||
| 1065 | + if (StringPrototypeStartsWith(specifier, 'node:')) { | ||
| 1066 | + normalized = BuiltinModule.normalizeRequirableId(specifier); | ||
| 1067 | + if (!normalized) { | ||
| 1068 | + throw new ERR_UNKNOWN_BUILTIN_MODULE(specifier); | ||
| 1064 | 1069 | } | |
| 1065 | - return Module._resolveFilename(specifier, parent, isMain, options).toString(); | ||
| 1070 | + return { __proto__: null, url: specifier, format: 'builtin', filename: normalized }; | ||
| 1066 | 1071 | } | |
| 1072 | + return wrapResolveFilename(specifier, parent, isMain, options); | ||
| 1073 | + } | ||
| 1067 | 1074 | ||
| 1075 | + /** | ||
| 1076 | + * Resolve a module request for CommonJS, invoking hooks from module.registerHooks() | ||
| 1077 | + * if necessary. | ||
| 1078 | + * @param {string} specifier | ||
| 1079 | + * @param {Module|undefined} parent | ||
| 1080 | + * @param {boolean} isMain | ||
| 1081 | + * @param {object} internalResolveOptions | ||
| 1082 | + * @param {boolean} internalResolveOptions.shouldSkipModuleHooks Whether to skip module hooks. | ||
| 1083 | + * @param {ResolveFilenameOptions} internalResolveOptions.requireResolveOptions Options from require.resolve(). | ||
| 1084 | + * Only used when it comes from require.resolve(). | ||
| 1085 | + * @returns {{url?: string, format?: string, parentURL?: string, filename: string}} | ||
| 1086 | + */ | ||
| 1087 | + function resolveForCJSWithHooks(specifier, parent, isMain, internalResolveOptions) { | ||
| 1088 | + const { requireResolveOptions, shouldSkipModuleHooks } = internalResolveOptions; | ||
| 1089 | + const defaultResolveImpl = requireResolveOptions ? | ||
| 1090 | + wrapResolveFilename : defaultResolveImplForCJSLoading; | ||
| 1068 | 1091 | // Fast path: no hooks, just return simple results. | |
| 1069 | 1092 | if (!resolveHooks.length || shouldSkipModuleHooks) { | |
| 1070 | - const filename = defaultResolveImpl(specifier, parent, isMain); | ||
| 1071 | - return { __proto__: null, url: defaultResolvedURL, filename, format }; | ||
| 1093 | + return defaultResolveImpl(specifier, parent, isMain, requireResolveOptions); | ||
| 1072 | 1094 | } | |
| 1073 | 1095 | ||
| 1074 | 1096 | // Slow path: has hooks, do the URL conversions and invoke hooks with contexts. | |
@@ -1097,27 +1119,25 @@ function resolveForCJSWithHooks(specifier, parent, isMain, shouldSkipModuleHooks | |||
| 1097 | 1119 | } else { | |
| 1098 | 1120 | conditionSet = getCjsConditions(); | |
| 1099 | 1121 | } | |
| 1100 | - defaultResolvedFilename = defaultResolveImpl(specifier, parent, isMain, { | ||
| 1122 | + | ||
| 1123 | + const result = defaultResolveImpl(specifier, parent, isMain, { | ||
| 1101 | 1124 | __proto__: null, | |
| 1125 | + paths: requireResolveOptions?.paths, | ||
| 1102 | 1126 | conditions: conditionSet, | |
| 1103 | 1127 | }); | |
| 1128 | + // If the default resolver does not return a URL, convert it for the public API. | ||
| 1129 | + result.url ??= convertCJSFilenameToURL(result.filename); | ||
| 1104 | 1130 | ||
| 1105 | - defaultResolvedURL = convertCJSFilenameToURL(defaultResolvedFilename); | ||
| 1106 | - return { __proto__: null, url: defaultResolvedURL }; | ||
| 1131 | + // Remove filename because it's not part of the public API. | ||
| 1132 | + // TODO(joyeecheung): maybe expose it in the public API to avoid re-conversion for users too. | ||
| 1133 | + return { __proto__: null, url: result.url, format: result.format }; | ||
| 1107 | 1134 | } | |
| 1108 | 1135 | ||
| 1109 | 1136 | const resolveResult = resolveWithHooks(specifier, parentURL, /* importAttributes */ undefined, | |
| 1110 | 1137 | getCjsConditionsArray(), defaultResolve); | |
| 1111 | - const { url } = resolveResult; | ||
| 1112 | - format = resolveResult.format; | ||
| 1113 | - | ||
| 1114 | - let filename; | ||
| 1115 | - if (url === defaultResolvedURL) { // Not overridden, skip the re-conversion. | ||
| 1116 | - filename = defaultResolvedFilename; | ||
| 1117 | - } else { | ||
| 1118 | - filename = convertURLToCJSFilename(url); | ||
| 1119 | - } | ||
| 1120 | - | ||
| 1138 | + const { url, format } = resolveResult; | ||
| 1139 | + // Convert the URL from the hook chain back to a filename for internal use. | ||
| 1140 | + const filename = convertURLToCJSFilename(url); | ||
| 1121 | 1141 | const result = { __proto__: null, url, format, filename, parentURL }; | |
| 1122 | 1142 | debug('resolveForCJSWithHooks', specifier, parent?.id, isMain, shouldSkipModuleHooks, '->', result); | |
| 1123 | 1143 | return result; | |
@@ -1212,10 +1232,10 @@ function loadBuiltinWithHooks(id, url, format) { | |||
| 1212 | 1232 | * @param {string} request Specifier of module to load via `require` | |
| 1213 | 1233 | * @param {Module} parent Absolute path of the module importing the child | |
| 1214 | 1234 | * @param {boolean} isMain Whether the module is the main entry point | |
| 1215 | - * @param {object|undefined} options Additional options for loading the module | ||
| 1235 | + * @param {object|undefined} internalResolveOptions Additional options for loading the module | ||
| 1216 | 1236 | * @returns {object} | |
| 1217 | 1237 | */ | |
| 1218 | - Module._load = function(request, parent, isMain, options = kEmptyObject) { | ||
| 1238 | + Module._load = function(request, parent, isMain, internalResolveOptions = kEmptyObject) { | ||
| 1219 | 1239 | let relResolveCacheIdentifier; | |
| 1220 | 1240 | if (parent) { | |
| 1221 | 1241 | debug('Module._load REQUEST %s parent: %s', request, parent.id); | |
@@ -1238,7 +1258,7 @@ Module._load = function(request, parent, isMain, options = kEmptyObject) { | |||
| 1238 | 1258 | } | |
| 1239 | 1259 | } | |
| 1240 | 1260 | ||
| 1241 | - const resolveResult = resolveForCJSWithHooks(request, parent, isMain, options.shouldSkipModuleHooks); | ||
| 1261 | + const resolveResult = resolveForCJSWithHooks(request, parent, isMain, internalResolveOptions); | ||
| 1242 | 1262 | let { format } = resolveResult; | |
| 1243 | 1263 | const { url, filename } = resolveResult; | |
| 1244 | 1264 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -722,13 +722,16 @@ class ModuleLoader { | |||
| 722 | 722 | * `module.registerHooks()` hooks. | |
| 723 | 723 | * @param {string} [parentURL] See {@link resolve}. | |
| 724 | 724 | * @param {ModuleRequest} request See {@link resolve}. | |
| 725 | + * @param {boolean} [shouldSkipSyncHooks] Whether to skip the synchronous hooks registered by module.registerHooks(). | ||
| 726 | + * This is used to maintain compatibility for the re-invented require.resolve (in imported CJS customized | ||
| 727 | + * by module.register()`) which invokes the CJS resolution separately from the hook chain. | ||
| 725 | 728 | * @returns {{ format: string, url: string }} | |
| 726 | 729 | */ | |
| 727 | - resolveSync(parentURL, request) { | ||
| 730 | + resolveSync(parentURL, request, shouldSkipSyncHooks = false) { | ||
| 728 | 731 | const specifier = `${request.specifier}`; | |
| 729 | 732 | const importAttributes = request.attributes ?? kEmptyObject; | |
| 730 | 733 | ||
| 731 | - if (syncResolveHooks.length) { | ||
| 734 | + if (!shouldSkipSyncHooks && syncResolveHooks.length) { | ||
| 732 | 735 | // Has module.registerHooks() hooks, chain the asynchronous hooks in the default step. | |
| 733 | 736 | return resolveWithSyncHooks(specifier, parentURL, importAttributes, this.#defaultConditions, | |
| 734 | 737 | this.#resolveAndMaybeBlockOnLoaderThread.bind(this)); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -94,6 +94,8 @@ translators.set('module', function moduleStrategy(url, translateContext, parentU | |||
| 94 | 94 | ||
| 95 | 95 | const { requestTypes: { kRequireInImportedCJS } } = require('internal/modules/esm/utils'); | |
| 96 | 96 | const kShouldSkipModuleHooks = { __proto__: null, shouldSkipModuleHooks: true }; | |
| 97 | + const kShouldNotSkipModuleHooks = { __proto__: null, shouldSkipModuleHooks: false }; | ||
| 98 | + | ||
| 97 | 99 | /** | |
| 98 | 100 | * Loads a CommonJS module via the ESM Loader sync CommonJS translator. | |
| 99 | 101 | * This translator creates its own version of the `require` function passed into CommonJS modules. | |
@@ -164,14 +166,17 @@ function loadCJSModule(module, source, url, filename, isMain) { | |||
| 164 | 166 | }; | |
| 165 | 167 | setOwnProperty(requireFn, 'resolve', function resolve(specifier) { | |
| 166 | 168 | if (!StringPrototypeStartsWith(specifier, 'node:')) { | |
| 167 | - const path = CJSModule._resolveFilename(specifier, module); | ||
| 168 | - if (specifier !== path) { | ||
| 169 | - specifier = `${pathToFileURL(path)}`; | ||
| 169 | + const { | ||
| 170 | + filename, url: resolvedURL, | ||
| 171 | + } = resolveForCJSWithHooks(specifier, module, false, kShouldNotSkipModuleHooks); | ||
| 172 | + if (specifier !== filename) { | ||
| 173 | + specifier = resolvedURL ?? `${pathToFileURL(filename)}`; | ||
| 170 | 174 | } | |
| 171 | 175 | } | |
| 172 | 176 | ||
| 173 | 177 | const request = { specifier, __proto__: null, attributes: kEmptyObject }; | |
| 174 | - const { url: resolvedURL } = cascadedLoader.resolveSync(url, request); | ||
| 178 | + // Skip sync hooks in resolveSync since resolveForCJSWithHooks already ran them above. | ||
| 179 | + const { url: resolvedURL } = cascadedLoader.resolveSync(url, request, /* shouldSkipSyncHooks */ true); | ||
| 175 | 180 | return urlToFilename(resolvedURL); | |
| 176 | 181 | }); | |
| 177 | 182 | setOwnProperty(requireFn, 'main', process.mainModule); | |
@@ -401,7 +406,7 @@ function cjsPreparseModuleExports(filename, source, format) { | |||
| 401 | 406 | let resolved; | |
| 402 | 407 | let format; | |
| 403 | 408 | try { | |
| 404 | - ({ format, filename: resolved } = resolveForCJSWithHooks(reexport, module, false)); | ||
| 409 | + ({ format, filename: resolved } = resolveForCJSWithHooks(reexport, module, false, kShouldNotSkipModuleHooks)); | ||
| 405 | 410 | } catch (e) { | |
| 406 | 411 | debug(`Failed to resolve '${reexport}', skipping`, e); | |
| 407 | 412 | continue; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -42,6 +42,7 @@ const { | |||
| 42 | 42 | flushCompileCache, | |
| 43 | 43 | } = internalBinding('modules'); | |
| 44 | 44 | ||
| 45 | + const lazyCJSLoader = getLazy(() => require('internal/modules/cjs/loader')); | ||
| 45 | 46 | let debug = require('internal/util/debuglog').debuglog('module', (fn) => { | |
| 46 | 47 | debug = fn; | |
| 47 | 48 | }); | |
@@ -198,7 +199,23 @@ function makeRequireFunction(mod) { | |||
| 198 | 199 | */ | |
| 199 | 200 | function resolve(request, options) { | |
| 200 | 201 | validateString(request, 'request'); | |
| 201 | - return Module._resolveFilename(request, mod, false, options); | ||
| 202 | + const { resolveForCJSWithHooks } = lazyCJSLoader(); | ||
| 203 | + // require.resolve() has different behaviors from the internal resolution used by | ||
| 204 | + // Module._load: | ||
| 205 | + // 1. When the request resolves to a non-existent built-in, it throws MODULE_NOT_FOUND | ||
| 206 | + // instead of UNKNOWN_BUILTIN_MODULE. This is handled by resolveForCJSWithHooks. | ||
| 207 | + // 2. If the request is a prefixed built-in, the returned value is also prefixed. This | ||
| 208 | + // is handled below. | ||
| 209 | + const { filename, url } = resolveForCJSWithHooks( | ||
| 210 | + request, mod, /* isMain */ false, { | ||
| 211 | + __proto__: null, | ||
| 212 | + shouldSkipModuleHooks: false, | ||
| 213 | + requireResolveOptions: options ?? {}, | ||
| 214 | + }); | ||
| 215 | + if (url === request && StringPrototypeStartsWith(request, 'node:')) { | ||
| 216 | + return url; | ||
| 217 | + } | ||
| 218 | + return filename; | ||
| 202 | 219 | } | |
| 203 | 220 | ||
| 204 | 221 | require.resolve = resolve; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,4 @@ | |||
| 1 | + // Fixture CJS file that calls require.resolve and exports the result. | ||
| 2 | + 'use strict'; | ||
| 3 | + const resolved = require.resolve('test-require-resolve-hook-target'); | ||
| 4 | + module.exports = { resolved }; | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,13 @@ | |||
| 1 | + // Fixture CJS file that calls require.resolve with the paths option. | ||
| 2 | + 'use strict'; | ||
| 3 | + const path = require('path'); | ||
| 4 | + const fixturesDir = path.resolve(__dirname, '..', '..'); | ||
| 5 | + const nodeModules = path.join(fixturesDir, 'node_modules'); | ||
| 6 | + | ||
| 7 | + // Use the paths option to resolve 'bar' from the fixtures node_modules. | ||
| 8 | + const resolved = require.resolve('bar', { paths: [fixturesDir] }); | ||
| 9 | + const expected = path.join(nodeModules, 'bar.js'); | ||
| 10 | + if (resolved !== expected) { | ||
| 11 | + throw new Error(`Expected ${expected}, got ${resolved}`); | ||
| 12 | + } | ||
| 13 | + module.exports = { resolved }; | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,25 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + // This tests that require.resolve() works with builtin redirection | ||
| 4 | + // via resolve hooks registered with module.registerHooks(). | ||
| 5 | + | ||
| 6 | + require('../common'); | ||
| 7 | + const assert = require('assert'); | ||
| 8 | + const { registerHooks } = require('module'); | ||
| 9 | + | ||
| 10 | + const hook = registerHooks({ | ||
| 11 | + resolve(specifier, context, nextResolve) { | ||
| 12 | + if (specifier === 'assert') { | ||
| 13 | + return { | ||
| 14 | + url: 'node:zlib', | ||
| 15 | + shortCircuit: true, | ||
| 16 | + }; | ||
| 17 | + } | ||
| 18 | + return nextResolve(specifier, context); | ||
| 19 | + }, | ||
| 20 | + }); | ||
| 21 | + | ||
| 22 | + const resolved = require.resolve('assert'); | ||
| 23 | + assert.strictEqual(resolved, 'zlib'); | ||
| 24 | + | ||
| 25 | + hook.deregister(); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,38 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + // This tests that require.resolve() and require() both go through the same | ||
| 4 | + // resolve hooks registered via module.registerHooks(). | ||
| 5 | + | ||
| 6 | + require('../common'); | ||
| 7 | + const assert = require('assert'); | ||
| 8 | + const { registerHooks } = require('module'); | ||
| 9 | + const fixtures = require('../common/fixtures'); | ||
| 10 | + const { pathToFileURL } = require('url'); | ||
| 11 | + | ||
| 12 | + const redirectedPath = fixtures.path('module-hooks', 'redirected-assert.js'); | ||
| 13 | + | ||
| 14 | + const resolvedSpecifiers = []; | ||
| 15 | + const hook = registerHooks({ | ||
| 16 | + resolve(specifier, context, nextResolve) { | ||
| 17 | + if (specifier === 'test-consistency-target') { | ||
| 18 | + resolvedSpecifiers.push(specifier); | ||
| 19 | + return { | ||
| 20 | + url: pathToFileURL(redirectedPath).href, | ||
| 21 | + shortCircuit: true, | ||
| 22 | + }; | ||
| 23 | + } | ||
| 24 | + return nextResolve(specifier, context); | ||
| 25 | + }, | ||
| 26 | + }); | ||
| 27 | + | ||
| 28 | + const resolveResult = require.resolve('test-consistency-target'); | ||
| 29 | + const requireResult = require('test-consistency-target'); | ||
| 30 | + | ||
| 31 | + assert.strictEqual(resolveResult, redirectedPath); | ||
| 32 | + assert.strictEqual(requireResult.exports_for_test, 'redirected assert'); | ||
| 33 | + assert.deepStrictEqual(resolvedSpecifiers, [ | ||
| 34 | + 'test-consistency-target', | ||
| 35 | + 'test-consistency-target', | ||
| 36 | + ]); | ||
| 37 | + | ||
| 38 | + hook.deregister(); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,31 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + // This tests that require.resolve() from a require function returned by | ||
| 4 | + // module.createRequire() goes through resolve hooks registered via | ||
| 5 | + // module.registerHooks(). | ||
| 6 | + | ||
| 7 | + require('../common'); | ||
| 8 | + const assert = require('assert'); | ||
| 9 | + const { registerHooks, createRequire } = require('module'); | ||
| 10 | + const fixtures = require('../common/fixtures'); | ||
| 11 | + const { pathToFileURL } = require('url'); | ||
| 12 | + | ||
| 13 | + const redirectedPath = fixtures.path('module-hooks', 'redirected-assert.js'); | ||
| 14 | + | ||
| 15 | + const hook = registerHooks({ | ||
| 16 | + resolve(specifier, context, nextResolve) { | ||
| 17 | + if (specifier === 'test-create-require-resolve-target') { | ||
| 18 | + return { | ||
| 19 | + url: pathToFileURL(redirectedPath).href, | ||
| 20 | + shortCircuit: true, | ||
| 21 | + }; | ||
| 22 | + } | ||
| 23 | + return nextResolve(specifier, context); | ||
| 24 | + }, | ||
| 25 | + }); | ||
| 26 | + | ||
| 27 | + const customRequire = createRequire(__filename); | ||
| 28 | + const resolved = customRequire.resolve('test-create-require-resolve-target'); | ||
| 29 | + assert.strictEqual(resolved, redirectedPath); | ||
| 30 | + | ||
| 31 | + hook.deregister(); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,19 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + // This tests that require.resolve() falls through to default resolution | ||
| 4 | + // when resolve hooks registered via module.registerHooks() don't override. | ||
| 5 | + | ||
| 6 | + require('../common'); | ||
| 7 | + const assert = require('assert'); | ||
| 8 | + const { registerHooks } = require('module'); | ||
| 9 | + | ||
| 10 | + const hook = registerHooks({ | ||
| 11 | + resolve(specifier, context, nextResolve) { | ||
| 12 | + return nextResolve(specifier, context); | ||
| 13 | + }, | ||
| 14 | + }); | ||
| 15 | + | ||
| 16 | + const resolved = require.resolve('assert'); | ||
| 17 | + assert.strictEqual(resolved, 'assert'); | ||
| 18 | + | ||
| 19 | + hook.deregister(); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments