@@ -66,6 +66,135 @@ const require = createRequire(import.meta.url);
6666const siblingModule = require (' ./sibling-module' );
6767` ` `
6868
69+ ### ` module .clearCache (specifier, options)`
70+
71+ <!-- YAML
72+ added: REPLACEME
73+ -->
74+
75+ > Stability: 1.0 - Early development
76+
77+ * ` specifier` {string|URL} The module specifier, as it would have been passed to
78+ ` import ()` or ` require ()` .
79+ * ` options` {Object}
80+ * ` parentURL` {string|URL} The parent URL used to resolve the specifier. Parent identity
81+ is part of the resolution cache key. For CommonJS, pass ` pathToFileURL (__filename )` .
82+ For ES modules, pass ` import .meta.url` .
83+ * ` resolver` {string} Specifies how resolution should be performed. Must be either
84+ ` ' import' ` or ` ' require' ` .
85+ * ` importAttributes` {Object} Optional import attributes. Only meaningful when
86+ ` resolver` is ` ' import' ` .
87+
88+ Clears the module resolution and module caches for a module. This enables
89+ reload patterns similar to deleting from ` require.cache` in CommonJS, and is useful for
90+ hot module reload.
91+
92+ The specifier is resolved using the chosen ` resolver` , then the resolved module is removed
93+ from all internal caches (CommonJS ` require` cache, CommonJS resolution caches, ESM resolve
94+ cache, ESM load cache, and ESM translators cache). When ` resolver` is ` ' import' ` ,
95+ ` importAttributes` are part of the ESM resolve-cache key, so only the exact
96+ ` (specifier, parentURL, importAttributes)` resolution entry is removed. When a ` file:` URL is
97+ resolved, cached module jobs for the same file path are cleared even if they differ by search
98+ or hash. This means clearing ` ' ./mod.mjs?v=1' ` will also clear ` ' ./mod.mjs?v=2' ` and any
99+ other query/hash variants that resolve to the same file.
100+
101+ When ` resolver` is ` ' require' ` , cached ` package.json` data for the resolved module's package
102+ is also cleared so that updated exports/imports conditions are picked up on the next
103+ resolution.
104+
105+ Clearing a module does not clear cached entries for its dependencies. When using
106+ ` resolver: ' import' ` , resolution cache entries for other specifiers that resolve to the
107+ same target are not cleared — only the exact ` (specifier, parentURL, importAttributes)`
108+ entry is removed. The module cache itself is cleared by resolved file path, so all
109+ specifiers pointing to the same file will see a fresh execution on next import.
110+
111+ #### Memory retention and static imports
112+
113+ ` clearCache ` only removes references from the Node.js **JavaScript-level** caches
114+ (the ESM load cache, resolve cache, CJS ` require.cache` , and related structures).
115+ It does **not** affect V8-internal module graph references.
116+
117+ When a module M is **statically imported** by a live parent module P
118+ (i.e., via a top-level ` import … from ' …' ` statement that has already been
119+ evaluated), V8's module instantiation creates a permanent internal strong
120+ reference from P's compiled module record to M's module record. Calling
121+ ` clearCache(M)` cannot sever that link. Consequences:
122+
123+ * The old instance of M **stays alive in memory** for as long as P is alive,
124+ regardless of how many times M is cleared and re-imported.
125+ * A fresh ` import(M)` after clearing will create a **separate** module instance
126+ that new importers see. P, however, continues to use the original instance —
127+ the two coexist simultaneously (sometimes called a "split-brain" state).
128+ * This is a **bounded** retention: one stale module instance per cleared module
129+ per live static parent. It does not grow unboundedly across clear/re-import
130+ cycles.
131+
132+ For **dynamically imported** modules (` await import(' ./M.mjs' )` with no live
133+ static parent holding the result), the old ` ModuleWrap` becomes eligible for
134+ garbage collection once ` clearCache ` removes it from Node.js caches and all
135+ JS-land references (e.g., stored namespace objects) are dropped.
136+
137+ The safest pattern for hot-reload of ES modules is to use cache-busting search
138+ parameters (so each version is a distinct module URL) and use dynamic imports for modules that need to be reloaded:
139+
140+ #### ECMA-262 spec considerations
141+
142+ Re-importing the exact same ` (specifier, parentURL, importAttributes)` tuple after clearing the module cache
143+ technically violates the idempotency invariant of the ECMA-262
144+ [` HostLoadImportedModule` ][] host hook, which expects that the same module request always
145+ returns the same Module Record for a given referrer. The result of violating this requirement
146+ is undefined — e.g. it can lead to crashes. For spec-compliant usage, use
147+ cache-busting search parameters so that each reload uses a distinct module request:
148+
149+ ` ` ` mjs
150+ import { clearCache } from ' node:module' ;
151+ import { watch } from ' node:fs' ;
152+
153+ let version = 0 ;
154+ const base = new URL (' ./app.mjs' , import .meta.url);
155+
156+ watch (base, async () => {
157+ // Clear the module cache for the previous version.
158+ clearCache (new URL (` ${ base .href } ?v=${ version} ` ), {
159+ parentURL: import .meta.url,
160+ resolver: ' import' ,
161+ });
162+ version++ ;
163+ // Re-import with a new search parameter — this is a distinct module request
164+ // and does not violate the ECMA-262 invariant.
165+ const mod = await import (` ${ base .href } ?v=${ version} ` );
166+ console .log (' reloaded:' , mod);
167+ });
168+ ` ` `
169+
170+ #### Examples
171+
172+ ` ` ` mjs
173+ import { clearCache } from ' node:module' ;
174+
175+ await import (' ./mod.mjs' );
176+
177+ clearCache (' ./mod.mjs' , {
178+ parentURL: import .meta.url,
179+ resolver: ' import' ,
180+ });
181+ await import (' ./mod.mjs' ); // re-executes the module
182+ ` ` `
183+
184+ ` ` ` cjs
185+ const { clearCache } = require (' node:module' );
186+ const { pathToFileURL } = require (' node:url' );
187+
188+ require (' ./mod.js' );
189+
190+ clearCache (' ./mod.js' , {
191+ parentURL: pathToFileURL (__filename ),
192+ resolver: ' require' ,
193+ });
194+ require (' ./mod.js' ); // eslint-disable-line node-core/no-duplicate-requires
195+ // re-executes the module
196+ ` ` `
197+
69198### ` module .findPackageJSON (specifier[, base])`
70199
71200<!-- YAML
@@ -2040,6 +2169,7 @@ returned object contains the following keys:
20402169[` -- enable- source- maps` ]: cli.md#--enable-source-maps
20412170[` -- import ` ]: cli.md#--importmodule
20422171[` --require` ]: cli.md#-r---require-module
2172+ [` HostLoadImportedModule` ]: https://tc39.es/ecma262/#sec-HostLoadImportedModule
20432173[` NODE_COMPILE_CACHE=dir` ]: cli.md#node_compile_cachedir
20442174[` NODE_COMPILE_CACHE_PORTABLE=1` ]: cli.md#node_compile_cache_portable1
20452175[` NODE_DISABLE_COMPILE_CACHE=1` ]: cli.md#node_disable_compile_cache1
0 commit comments