@@ -66,6 +66,201 @@ 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 ()` . When ` resolver` is ` ' require' ` , this must be a
79+ string (the same kind of path or identifier ` require ()` accepts). Passing a
80+ ` URL ` object with ` resolver: ' require' ` throws ` ERR_INVALID_ARG_TYPE ` .
81+ * ` options` {Object} Required.
82+ * ` parentURL` {string|URL} Required. The parent URL used to resolve the
83+ specifier. Parent identity is part of the resolution cache key. For
84+ CommonJS, pass ` pathToFileURL (__filename )` . For ES modules, pass
85+ ` import .meta.url` .
86+ * ` resolver` {string} Required. How resolution should be performed. Must be
87+ either ` ' import' ` or ` ' require' ` .
88+ * ` importAttributes` {Object} Optional import attributes. Only meaningful when
89+ ` resolver` is ` ' import' ` .
90+
91+ Clears the module resolution and module caches for a module. This enables
92+ reload patterns similar to deleting from ` require.cache` in CommonJS, and is
93+ useful for hot module reload.
94+
95+ Both ` options.parentURL` and ` options.resolver` are required. There is no
96+ recursive option: ` clearCache` invalidates only the resolved module, not its
97+ dependencies. Callers that need to reload a graph must track and clear each
98+ module themselves.
99+
100+ The specifier is resolved using the chosen ` resolver` , then the resolved module
101+ is removed from all Node.js internal caches (CommonJS ` require` cache, CommonJS
102+ resolution caches, ESM resolve cache, ESM load cache, and ESM translators
103+ cache). When ` resolver` is ` ' import' ` , ` importAttributes` are part of the ESM
104+ resolve-cache key, so only the exact ` (specifier, parentURL, importAttributes)`
105+ resolution entry is removed. When a ` file:` URL is resolved, cached module jobs
106+ for the same file path are cleared even if they differ by search or hash. This
107+ means clearing ` ' ./mod.mjs?v=1' ` will also clear ` ' ./mod.mjs?v=2' ` and any
108+ other query/hash variants that resolve to the same file.
109+
110+ When ` resolver` is ` ' require' ` , cached ` package.json` data for the resolved
111+ module's package is also cleared so that updated exports/imports conditions are
112+ picked up on the next resolution.
113+
114+ Clearing a module does not clear cached entries for its dependencies. When using
115+ ` resolver: ' import' ` , resolution cache entries for other specifiers that resolve
116+ to the same target are not cleared — only the exact
117+ ` (specifier, parentURL, importAttributes)` entry is removed. The module cache
118+ itself is cleared by resolved file path, so all specifiers pointing to the same
119+ file will see a fresh execution on next import.
120+
121+ #### Memory retention and static imports
122+
123+ ` clearCache ` only removes references from the Node.js internal caches (the ESM
124+ load cache, resolve cache, CJS ` require.cache` , and related structures). It does
125+ **not** affect references created by user modules, for example through a static
126+ ` import` . If one module imports another, ` clearCache` will not clean up that
127+ link. It only clears references from Node.js internal caches to user modules.
128+
129+ When a module M is **statically imported** by a live parent module P (via a
130+ top-level ` import … from ' …' ` statement that has already been evaluated), the
131+ engine keeps a permanent internal strong reference from P's compiled module
132+ record to M's module record. Calling ` clearCache(M)` cannot sever that link.
133+ Consequences:
134+
135+ * The old instance of M **stays alive in memory** for as long as P is alive,
136+ regardless of how many times M is cleared and re-imported.
137+ * A fresh ` import(M)` after clearing will create a **separate** module instance
138+ that new importers see. P, however, continues to use the original instance —
139+ the two coexist simultaneously (sometimes called a "split-brain" state).
140+ * This is a **bounded** retention: one stale module instance per cleared module
141+ per live static parent. It does not grow unboundedly across clear/re-import
142+ cycles.
143+
144+ For **dynamically imported** modules (` await import(' ./M.mjs' )` with no live
145+ static parent holding the result), the old module becomes eligible for
146+ garbage collection once ` clearCache ` removes it from Node.js caches and all
147+ JavaScript references (for example, stored namespace objects) are dropped.
148+
149+ The safest pattern for hot-reload of ES modules is to use cache-busting search
150+ parameters (so each version is a distinct module URL) and use dynamic imports
151+ for modules that need to be reloaded:
152+
153+ #### ECMA-262 spec considerations
154+
155+ Re-importing the exact same ` (specifier, parentURL, importAttributes)` tuple after clearing the module cache
156+ technically violates the idempotency invariant of the ECMA-262
157+ [` HostLoadImportedModule` ][] host hook, which expects that the same module request always
158+ returns the same Module Record for a given referrer. The result of violating this requirement
159+ is undefined — e.g. it can lead to crashes. For spec-compliant usage, use
160+ cache-busting search parameters so that each reload uses a distinct module request:
161+
162+ ` ` ` mjs
163+ import { clearCache } from ' node:module' ;
164+ import { watch } from ' node:fs' ;
165+
166+ let version = 0 ;
167+ const base = new URL (' ./app.mjs' , import .meta.url);
168+
169+ watch (base, async () => {
170+ // Clear the module cache for the previous version.
171+ clearCache (new URL (` ${ base .href } ?v=${ version} ` ), {
172+ parentURL: import .meta.url,
173+ resolver: ' import' ,
174+ });
175+ version++ ;
176+ // Re-import with a new search parameter — this is a distinct module request
177+ // and does not violate the ECMA-262 invariant.
178+ const mod = await import (` ${ base .href } ?v=${ version} ` );
179+ console .log (' reloaded:' , mod);
180+ });
181+ ` ` `
182+
183+ #### Examples
184+
185+ Relative specifiers are resolved against ` parentURL` , not against the process
186+ working directory:
187+
188+ ` ` ` mjs
189+ import { clearCache } from ' node:module' ;
190+
191+ // Resolves to the `mod.mjs` sibling of *this* module, then clears it.
192+ await import (' ./mod.mjs' );
193+ clearCache (' ./mod.mjs' , {
194+ parentURL: import .meta.url,
195+ resolver: ' import' ,
196+ });
197+ await import (' ./mod.mjs' ); // re-executes the module
198+ ` ` `
199+
200+ ` ` ` cjs
201+ const { clearCache } = require (' node:module' );
202+ const { pathToFileURL } = require (' node:url' );
203+
204+ require (' ./mod.js' );
205+
206+ clearCache (' ./mod.js' , {
207+ parentURL: pathToFileURL (__filename ),
208+ resolver: ' require' ,
209+ });
210+ require (' ./mod.js' ); // eslint-disable-line node-core/no-duplicate-requires
211+ // re-executes the module
212+ ` ` `
213+
214+ Bare specifiers are resolved the same way ` import ` /` require` would resolve them
215+ from ` parentURL` (including ` node_modules` lookup and ` package.json` ` " exports" ` ):
216+
217+ ` ` ` mjs
218+ import { clearCache } from ' node:module' ;
219+
220+ await import (' some-package' );
221+ clearCache (' some-package' , {
222+ parentURL: import .meta.url,
223+ resolver: ' import' ,
224+ });
225+ await import (' some-package' ); // re-executes the package entry point
226+ ` ` `
227+
228+ An absolute ` file: ` URL still requires ` parentURL` and ` resolver` . The URL is
229+ the cache key; ` parentURL` is used if the loader needs to resolve it again
230+ (for example, through customization hooks):
231+
232+ ` ` ` mjs
233+ import { clearCache } from ' node:module' ;
234+
235+ const url = new URL (' ./mod.mjs' , import .meta.url);
236+ await import (url);
237+ clearCache (url, {
238+ parentURL: import .meta.url,
239+ resolver: ' import' ,
240+ });
241+ await import (url); // re-executes the module
242+ ` ` `
243+
244+ Reloading a CommonJS module between tests (the ESM equivalent should use
245+ cache-busting search parameters; see [ECMA-262 spec considerations][]):
246+
247+ ` ` ` cjs
248+ const { clearCache } = require (' node:module' );
249+ const { pathToFileURL } = require (' node:url' );
250+
251+ function loadFresh () {
252+ clearCache (' ./app.js' , {
253+ parentURL: pathToFileURL (__filename ),
254+ resolver: ' require' ,
255+ });
256+ return require (' ./app.js' );
257+ }
258+
259+ const first = loadFresh ();
260+ const second = loadFresh ();
261+ // `first` and `second` are independently evaluated copies.
262+ ` ` `
263+
69264### ` module .findPackageJSON (specifier[, base])`
70265
71266<!-- YAML
@@ -2072,6 +2267,7 @@ returned object contains the following keys:
20722267[CommonJS]: modules.md
20732268[Conditional exports]: packages.md#conditional-exports
20742269[Customization hooks]: #customization-hooks
2270+ [ECMA-262 spec considerations]: #ecma-262-spec-considerations
20752271[ES Modules]: esm.md
20762272[Permission Model]: permissions.md#permission-model
20772273[Source Map]: https://tc39.es/ecma426/
@@ -2082,6 +2278,7 @@ returned object contains the following keys:
20822278[` -- enable- source- maps` ]: cli.md#--enable-source-maps
20832279[` -- import ` ]: cli.md#--importmodule
20842280[` --require` ]: cli.md#-r---require-module
2281+ [` HostLoadImportedModule` ]: https://tc39.es/ecma262/#sec-HostLoadImportedModule
20852282[` NODE_COMPILE_CACHE=dir` ]: cli.md#node_compile_cachedir
20862283[` NODE_COMPILE_CACHE_PORTABLE=1` ]: cli.md#node_compile_cache_portable1
20872284[` NODE_COMPILE_CACHE_READONLY=1` ]: cli.md#node_compile_cache_readonly1
0 commit comments