@@ -10,9 +10,9 @@ const {
1010 StringPrototypeStartsWith,
1111} = primordials ;
1212
13- const { Module, resolveForCJSWithHooks } = require ( 'internal/modules/cjs/loader' ) ;
13+ const { Module, resolveForCJSWithHooks, clearCJSResolutionCaches } = require ( 'internal/modules/cjs/loader' ) ;
1414const { fileURLToPath, isURL, URLParse, pathToFileURL } = require ( 'internal/url' ) ;
15- const { kEmptyObject, isWindows } = require ( 'internal/util' ) ;
15+ const { emitExperimentalWarning , kEmptyObject, isWindows } = require ( 'internal/util' ) ;
1616const { validateObject, validateOneOf, validateString } = require ( 'internal/validators' ) ;
1717const {
1818 codes : {
@@ -87,6 +87,10 @@ function createParentModuleForClearCache(parentPath) {
8787
8888/**
8989 * Resolve a cache filename for CommonJS.
90+ * Always goes through resolveForCJSWithHooks so that registered hooks
91+ * are respected. For file: URLs, search/hash are stripped before resolving
92+ * since CJS operates on file paths. For non-file URLs, the specifier is
93+ * passed as-is to let hooks handle it.
9094 * @param {string|URL } specifier
9195 * @param {string|undefined } parentPath
9296 * @returns {string|null }
@@ -99,44 +103,47 @@ function resolveClearCacheFilename(specifier, parentPath) {
99103 const parsedURL = getURLFromClearCacheSpecifier ( specifier ) ;
100104 let request = specifier ;
101105 if ( parsedURL ) {
102- if ( parsedURL . protocol !== 'file:' || parsedURL . search !== '' || parsedURL . hash !== '' ) {
103- return null ;
106+ if ( parsedURL . protocol === 'file:' ) {
107+ // Strip search/hash - CJS operates on file paths.
108+ if ( parsedURL . search !== '' || parsedURL . hash !== '' ) {
109+ parsedURL . search = '' ;
110+ parsedURL . hash = '' ;
111+ }
112+ request = fileURLToPath ( parsedURL ) ;
113+ } else {
114+ // Non-file URLs (e.g. virtual://) — pass the href as-is
115+ // so that registered hooks can resolve them.
116+ request = parsedURL . href ;
104117 }
105- request = fileURLToPath ( parsedURL ) ;
106118 }
107119
108120 const parent = parentPath ? createParentModuleForClearCache ( parentPath ) : null ;
109- const { filename, format } = resolveForCJSWithHooks ( request , parent , false , false ) ;
110- if ( format === 'builtin' ) {
121+ try {
122+ const { filename, format } = resolveForCJSWithHooks ( request , parent , false , false ) ;
123+ if ( format === 'builtin' ) {
124+ return null ;
125+ }
126+ return filename ;
127+ } catch {
128+ // Resolution can fail for non-file specifiers without hooks — return null
129+ // to silently skip clearing rather than throwing.
111130 return null ;
112131 }
113- return filename ;
114132}
115133
116134/**
117135 * Resolve a cache URL for ESM.
136+ * Always goes through the loader's resolveSync so that registered hooks
137+ * (e.g. hooks that redirect specifiers) are respected.
118138 * @param {string|URL } specifier
119- * @param {string|undefined } parentURL
139+ * @param {string } parentURL
120140 * @returns {string }
121141 */
122142function resolveClearCacheURL ( specifier , parentURL ) {
123- const parsedURL = getURLFromClearCacheSpecifier ( specifier ) ;
124- if ( parsedURL != null ) {
125- return parsedURL . href ;
126- }
127-
128- if ( path . isAbsolute ( specifier ) ) {
129- return pathToFileURL ( specifier ) . href ;
130- }
131-
132- if ( parentURL === undefined ) {
133- throw new ERR_INVALID_ARG_VALUE ( 'options.parentURL' , parentURL ,
134- 'must be provided for non-URL ESM specifiers' ) ;
135- }
136-
137143 const cascadedLoader =
138144 require ( 'internal/modules/esm/loader' ) . getOrInitializeCascadedLoader ( ) ;
139- const request = { specifier, __proto__ : null } ;
145+ const specifierStr = isURL ( specifier ) ? specifier . href : specifier ;
146+ const request = { specifier : specifierStr , __proto__ : null } ;
140147 return cascadedLoader . resolveSync ( parentURL , request ) . url ;
141148}
142149
@@ -253,6 +260,8 @@ function isRelative(pathToCheck) {
253260 * }} options
254261 */
255262function clearCache ( specifier , options ) {
263+ emitExperimentalWarning ( 'module.clearCache' ) ;
264+
256265 const isSpecifierURL = isURL ( specifier ) ;
257266 if ( ! isSpecifierURL ) {
258267 validateString ( specifier , 'specifier' ) ;
@@ -273,13 +282,13 @@ function clearCache(specifier, options) {
273282 const clearResolution = caches === 'resolution' || caches === 'all' ;
274283 const clearModule = caches === 'module' || caches === 'all' ;
275284
276- // Resolve the specifier when module cache clearing is needed.
285+ // Resolve the specifier when module or resolution cache clearing is needed.
277286 // Must be done BEFORE clearing resolution caches since resolution
278287 // may rely on the resolve cache.
279288 let resolvedFilename = null ;
280289 let resolvedURL = null ;
281290
282- if ( clearModule ) {
291+ if ( clearModule || clearResolution ) {
283292 if ( resolver === 'require' ) {
284293 resolvedFilename = resolveClearCacheFilename ( specifier , parentPath ) ;
285294 if ( resolvedFilename ) {
@@ -293,13 +302,31 @@ function clearCache(specifier, options) {
293302 }
294303 }
295304
296- // Clear resolution cache. Only ESM has a structured resolution cache;
297- // CJS resolution results are not separately cached.
298- if ( clearResolution && resolver === 'import' ) {
299- const specifierStr = isSpecifierURL ? specifier . href : specifier ;
300- const cascadedLoader =
301- require ( 'internal/modules/esm/loader' ) . getOrInitializeCascadedLoader ( ) ;
302- cascadedLoader . deleteResolveCacheEntry ( specifierStr , parentURL , importAttributes ) ;
305+ // Clear resolution caches.
306+ if ( clearResolution ) {
307+ // ESM has a structured resolution cache keyed by (specifier, parentURL,
308+ // importAttributes).
309+ if ( resolver === 'import' ) {
310+ const specifierStr = isSpecifierURL ? specifier . href : specifier ;
311+ const cascadedLoader =
312+ require ( 'internal/modules/esm/loader' ) . getOrInitializeCascadedLoader ( ) ;
313+ cascadedLoader . deleteResolveCacheEntry ( specifierStr , parentURL , importAttributes ) ;
314+ }
315+
316+ // CJS has relativeResolveCache and Module._pathCache that map
317+ // specifiers to filenames. Clear entries pointing to the resolved file.
318+ if ( resolvedFilename ) {
319+ clearCJSResolutionCaches ( resolvedFilename ) ;
320+
321+ // Clear package.json caches for the resolved module's package so that
322+ // updated exports/imports conditions are picked up on re-resolution.
323+ const { getNearestParentPackageJSON, clearPackageJSONCache } =
324+ require ( 'internal/modules/package_json_reader' ) ;
325+ const pkg = getNearestParentPackageJSON ( resolvedFilename ) ;
326+ if ( pkg ?. path ) {
327+ clearPackageJSONCache ( pkg . path ) ;
328+ }
329+ }
303330 }
304331
305332 // Clear module caches everywhere in Node.js.
@@ -311,6 +338,10 @@ function clearCache(specifier, options) {
311338 delete Module . _cache [ resolvedFilename ] ;
312339 deleteModuleFromParents ( cachedModule ) ;
313340 }
341+ // Also clear CJS resolution caches that point to this filename,
342+ // even if only 'module' was requested, to avoid stale resolution
343+ // results pointing to a purged module.
344+ clearCJSResolutionCaches ( resolvedFilename ) ;
314345 }
315346
316347 // ESM load cache and translators cjsCache
0 commit comments