Skip to content
Open
70 changes: 70 additions & 0 deletions doc/api/module.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,76 @@ const require = createRequire(import.meta.url);
const siblingModule = require('./sibling-module');
```

### `module.clearCache(specifier, options)`

<!-- YAML
added: REPLACEME
-->

> Stability: 1.1 - Active development

* `specifier` {string|URL} The module specifier, as it would have been passed to
`import()` or `require()`.
* `options` {Object}
* `parentURL` {string|URL} The parent URL used to resolve the specifier. Parent identity
is part of the resolution cache key. For CommonJS, pass `pathToFileURL(__filename)`.
For ES modules, pass `import.meta.url`.
* `resolver` {string} Specifies how resolution should be performed. Must be either
`'import'` or `'require'`.
* `caches` {string} Specifies which caches to clear. Must be one of:
* `'resolution'` — only clear the resolution cache entry for this specifier.
* `'module'` — clear the cached module everywhere in Node.js (not counting
JS-level references).
* `'all'` — clear both resolution and module caches.
* `importAttributes` {Object} Optional import attributes. Only meaningful when
`resolver` is `'import'`.

Clears module resolution and/or module caches for a module. This enables
reload patterns similar to deleting from `require.cache` in CommonJS, and is useful for HMR.
Copy link
Member

@joyeecheung joyeecheung Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may need to spell out a bit more concretely how HMR is supposed to use this API without leaking more. From #61767 (comment) there are a couple of things that need to be taken care of:

  1. The solution must clear the cache for the module that has changed
  2. The solution must also force a "re-linking" of the module graph so that dependent of the changed module see the update (the mechanics of this may involve more cache clearing as it bubbles up).

2 has a subtle difference in CommonJS v.s. ESM dependents. If the changed module has CommonJS dependents, the solution may need to track and clear the cache of all the dependent modules and re-evaluate from the root to see the update. For ESM dependents it's easier because evaluation is separate from linking, so the solution can simply clear the cache for the changed module and the root, and re-evaluate from the root to pick up the new branch while reusing all the existing branches (so unchanged modules won't get evaluated again). This won't violate the spec as explained by Nicolo because it's technically disposing the old graph and then linking and evaluating a new graph again that just reuse some existing module records, not mutating the existing link of an old, evaluated graph.

Although, it would be much simpler if whatever that uses it enforces that the module that gets HMR can only ever be the root itself and no bottom-up dependency HMR is supported. But I am not sure if that's a limitation that all existing HMR users can enforce.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be simpler if instead of a “clear cache” API, we just provided a “replace module” API? Where the new module must have the same exports as the old one, so that the linking wouldn’t need to be redone?

Copy link
Member

@joyeecheung joyeecheung Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think regardless of whether the new module has the same exports or not, linking must be redone. Links exist between different module records (not Node.js ModuleJobs or ModuleWraps, but v8::Module), and we can't swap out what's inside a v8::Module - but can only compile a new one and relink.


When `caches` is `'module'` or `'all'`, the specifier is resolved using the chosen `resolver`
and the resolved module is removed from all internal caches (CommonJS `require` cache, ESM
load cache, and ESM translators cache). When a `file:` URL is resolved, cached module jobs for
the same file path are cleared even if they differ by search or hash.

When `caches` is `'resolution'` or `'all'` with `resolver` set to `'import'`, the ESM
resolution cache entry for the given `(specifier, parentURL, importAttributes)` tuple is
cleared. CJS does not maintain a separate resolution cache.

Clearing a module does not clear cached entries for its dependencies, and other specifiers
that resolve to the same target may remain. Use consistent specifiers, or call `clearCache()`
for each specifier you want to re-execute.

```mjs
import { clearCache } from 'node:module';

const url = new URL('./mod.mjs', import.meta.url);
await import(url.href);

clearCache(url, {
parentURL: import.meta.url,
resolver: 'import',
caches: 'module',
});
await import(url.href); // re-executes the module
```

```cjs
const { clearCache } = require('node:module');
const { pathToFileURL } = require('node:url');
const path = require('node:path');

const file = path.join(__dirname, 'mod.js');
require(file);

clearCache(file, {
parentURL: pathToFileURL(__filename),
resolver: 'require',
caches: 'module',
});
require(file); // re-executes the module
```

### `module.findPackageJSON(specifier[, base])`

<!-- YAML
Expand Down
Loading
Loading