Skip to content

Commit 357917f

Browse files
committed
fixup! module: add clearCache for CJS and ESM
1 parent b7788b4 commit 357917f

8 files changed

Lines changed: 110 additions & 4 deletions

File tree

β€Ždoc/api/module.mdβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ Clears the CommonJS `require` cache and/or the ESM module cache for a module. Th
8989
reload patterns similar to deleting from `require.cache` in CommonJS, and is useful for HMR.
9090
When `mode` is `'all'`, resolution failures for one module system do not throw; check the
9191
returned flags to see what was cleared.
92+
This also clears internal resolution caches for the resolved module.
9293
9394
```mjs
9495
import { clearCache } from 'node:module';

β€Žlib/internal/modules/cjs/loader.jsβ€Ž

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2162,6 +2162,43 @@ function resolveClearCacheURL(specifier, parentURL, importAttributes) {
21622162
return cascadedLoader.resolveSync(parentURL, request).url;
21632163
}
21642164

2165+
/**
2166+
* Remove path cache entries that resolve to a filename.
2167+
* @param {string} filename
2168+
* @returns {boolean} true if any entries were deleted.
2169+
*/
2170+
function deletePathCacheEntries(filename) {
2171+
const cache = Module._pathCache;
2172+
const keys = ObjectKeys(cache);
2173+
let deleted = false;
2174+
for (let i = 0; i < keys.length; i++) {
2175+
const key = keys[i];
2176+
if (cache[key] === filename) {
2177+
delete cache[key];
2178+
deleted = true;
2179+
}
2180+
}
2181+
return deleted;
2182+
}
2183+
2184+
/**
2185+
* Remove relative resolve cache entries that resolve to a filename.
2186+
* @param {string} filename
2187+
* @returns {boolean} true if any entries were deleted.
2188+
*/
2189+
function deleteRelativeResolveCacheEntries(filename) {
2190+
const keys = ObjectKeys(relativeResolveCache);
2191+
let deleted = false;
2192+
for (let i = 0; i < keys.length; i++) {
2193+
const key = keys[i];
2194+
if (relativeResolveCache[key] === filename) {
2195+
delete relativeResolveCache[key];
2196+
deleted = true;
2197+
}
2198+
}
2199+
return deleted;
2200+
}
2201+
21652202
/**
21662203
* Clear CommonJS and/or ESM module cache entries.
21672204
* @param {string|URL} specifier
@@ -2201,9 +2238,19 @@ function clearCache(specifier, options = kEmptyObject) {
22012238
if (mode !== 'esm') {
22022239
try {
22032240
const filename = resolveClearCacheFilename(specifier, parentPath);
2204-
if (filename && Module._cache[filename] !== undefined) {
2205-
delete Module._cache[filename];
2206-
result.cjs = true;
2241+
if (filename) {
2242+
let deleted = false;
2243+
if (Module._cache[filename] !== undefined) {
2244+
delete Module._cache[filename];
2245+
deleted = true;
2246+
}
2247+
if (deletePathCacheEntries(filename)) {
2248+
deleted = true;
2249+
}
2250+
if (deleteRelativeResolveCacheEntries(filename)) {
2251+
deleted = true;
2252+
}
2253+
result.cjs = deleted;
22072254
}
22082255
} catch (err) {
22092256
if (mode === 'cjs') {
@@ -2217,7 +2264,9 @@ function clearCache(specifier, options = kEmptyObject) {
22172264
const url = resolveClearCacheURL(specifier, parentURL, importAttributes);
22182265
const cascadedLoader =
22192266
require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
2220-
result.esm = cascadedLoader.loadCache.deleteAll(url);
2267+
const loadDeleted = cascadedLoader.loadCache.deleteAll(url);
2268+
const resolveDeleted = cascadedLoader.deleteResolveCache(url);
2269+
result.esm = loadDeleted || resolveDeleted;
22212270
} catch (err) {
22222271
if (mode === 'esm') {
22232272
throw err;

β€Žlib/internal/modules/esm/loader.jsβ€Ž

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,15 @@ class ModuleLoader {
169169
*/
170170
loadCache = newLoadCache();
171171

172+
/**
173+
* Delete cached resolutions that resolve to a URL.
174+
* @param {string} url
175+
* @returns {boolean} true if any entries were deleted.
176+
*/
177+
deleteResolveCache(url) {
178+
return this.#resolveCache.deleteByResolvedURL(url);
179+
}
180+
172181
/**
173182
* @see {AsyncLoaderHooks.isForAsyncLoaderHookWorker}
174183
* Shortcut to this.#asyncLoaderHooks.isForAsyncLoaderHookWorker.

β€Žlib/internal/modules/esm/module_map.jsβ€Ž

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,31 @@ class ResolveCache extends SafeMap {
8585
has(serializedKey, parentURL) {
8686
return serializedKey in this.#getModuleCachedImports(parentURL);
8787
}
88+
89+
/**
90+
* Delete cached resolutions that resolve to a URL.
91+
* @param {string} url
92+
* @returns {boolean} true if any entries were deleted.
93+
*/
94+
deleteByResolvedURL(url) {
95+
validateString(url, 'url');
96+
let deleted = false;
97+
for (const [parentURL, entries] of this) {
98+
const keys = ObjectKeys(entries);
99+
for (let i = 0; i < keys.length; i++) {
100+
const key = keys[i];
101+
if (entries[key]?.url === url) {
102+
delete entries[key];
103+
deleted = true;
104+
}
105+
}
106+
107+
if (ObjectKeys(entries).length === 0) {
108+
super.delete(parentURL);
109+
}
110+
}
111+
return deleted;
112+
}
88113
}
89114

90115
/**

β€Žtest/es-module/test-module-clear-cache.mjsβ€Ž

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,16 @@ assert.strictEqual(result.esm, true);
1919
const third = await import(specifier);
2020
assert.strictEqual(third.count, 2);
2121

22+
const nested = new URL('../fixtures/module-cache/esm-nested-a.mjs', import.meta.url);
23+
const nestedFirst = await import(`${nested.href}?v=1`);
24+
assert.strictEqual(nestedFirst.value, 1);
25+
26+
const nestedResult = clearCache(new URL('../fixtures/module-cache/esm-nested-b.mjs', import.meta.url));
27+
assert.strictEqual(nestedResult.cjs, false);
28+
assert.strictEqual(nestedResult.esm, true);
29+
30+
const nestedSecond = await import(`${nested.href}?v=2`);
31+
assert.strictEqual(nestedSecond.value, 2);
32+
2233
delete globalThis.__module_cache_esm_counter;
34+
delete globalThis.__module_cache_esm_nested_c_counter;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { value } from './esm-nested-b.mjs';
2+
3+
export { value };
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { count } from './esm-nested-c.mjs';
2+
3+
export const value = count;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
globalThis.__module_cache_esm_nested_c_counter =
2+
(globalThis.__module_cache_esm_nested_c_counter ?? 0) + 1;
3+
4+
export const count = globalThis.__module_cache_esm_nested_c_counter;

0 commit comments

Comments
Β (0)