stealthy-require
stealthy-require is a small CommonJS cache-manipulation helper. It snapshots `require.cache`, removes every cached JavaScript module, runs your synchronous callback so its `require()` calls create fresh module instances, removes those new cache entries, and restores the original cache. An optional callback can preload selected dependencies that should stay cached. It affects the process-wide CommonJS loader, not ECMAScript module imports.
Do not use this as hot reload or general test isolation. It is acceptable only in controlled legacy CommonJS code where loads are synchronous, errors are contained, and process-wide cache mutation is understood.
Use it if
- You maintain a legacy CommonJS test suite that must instantiate a module and its dependency tree from scratch
- You know the complete synchronous require graph and need selected singleton dependencies preserved
- You are debugging module initialization and can run the load in an isolated short-lived process
- You are supporting an existing package that already depends on this exact cache restoration behavior
- You use ESM or dynamic `import()`: the package only manipulates the CommonJS `require.cache` object and cannot clear Node's ESM module map
- The load can throw: version 1.1.1 does not use `try/finally`, so an exception from either callback can leave the process-wide cache cleared or partially rebuilt
- Your module loads dependencies later from a timer, promise, event, or method: the README states only synchronous requires inside the callback bypass the cache
- Your dependency tree includes native `.node` addons: the implementation deliberately leaves them cached because native modules cannot safely be loaded twice
- You need reliable isolation in a server or concurrent test runner: it temporarily removes almost every CommonJS module from a global process cache, and repeated fresh loads also accumulate `module.children` references unless you clean them up
Setup reality
Installation is `npm install stealthy-require`; it has no runtime dependencies, native build of its own, peers, credentials, or config files. Its entire contract depends on CommonJS internals. You must pass the actual `require.cache` object plus a synchronous callback that performs and returns the desired `require()`. The helper snapshots the whole cache, deletes every entry whose filename does not end in `.node`, executes the callback, clears again, and copies the old entries back. This is process-wide mutable state, not a per-module sandbox. Do not overlap calls, invoke it while other code is loading modules, or treat it as request-safe hot reload. The most serious implementation surprise is error handling: neither callback is protected by `try/finally`. If loading throws, restoration code is skipped and subsequent requires can create unexpected duplicate singletons. Wrap calls with your own cache snapshot and restoration or, better, isolate risky loads in a child process. Fresh modules are also appended to the caller's `module.children`; the README shows snapshotting and restoring that array to avoid retaining every discarded instance. The optional keep callback needs the caller's `module` object as a fourth argument and must synchronously require every module to preserve. Native addons always remain cached, while lazy requires after the main callback return use the normal restored cache. There are no TypeScript declarations or ESM exports. The Webpack and Browserify instructions target old bundler internals, including Browserify's sixth wrapper argument, and should not be assumed to work in modern bundlers. In application code, module factories, explicit dependency injection, test-runner module reset features, or a new child process are safer designs.
Patterns
Load a fresh CommonJS dependency treerequire-fresh-module
const stealthyRequire = require('stealthy-require');
const freshClient = stealthyRequire(require.cache, () => {
return require('./client');
});Every non-native CommonJS module synchronously required by `./client` is loaded against an empty cache, not only the top-level module.
Verify that the original cache is restoredcompare-fresh-instance
const original = require('./counter');
const fresh = stealthyRequire(require.cache, () => require('./counter'));
const restored = require('./counter');
console.log(original === restored); // true
console.log(original === fresh); // falseThe fresh instance is deliberately absent from `require.cache` after the call, while the original instance is copied back.
Keep one dependency cachedpreserve-singleton-dependency
const freshApp = stealthyRequire(
require.cache,
() => require('./app'),
() => {
require('./database-pool');
require('./metrics-registry');
},
module,
);The keep callback and caller's `module` argument are both required for this mode. List every singleton that must not be duplicated.
Restore the cache when loading throwsrestore-cache-after-error
function loadFreshSafely(load) {
const snapshot = { ...require.cache };
try {
return stealthyRequire(require.cache, load);
} finally {
for (const id of Object.keys(require.cache)) delete require.cache[id];
Object.assign(require.cache, snapshot);
}
}
const plugin = loadFreshSafely(() => require('./plugin'));Version 1.1.1 has no internal `try/finally`. This outer guard is necessary when the callback can throw, though a child process gives stronger isolation.
Remove discarded modules from module.childrenavoid-child-retention
const childrenBefore = module.children.slice();
try {
const fresh = stealthyRequire(require.cache, () => require('./worker'));
fresh.run();
} finally {
module.children = childrenBefore;
}Cache restoration does not remove fresh instances from the caller's `module.children`; repeated loads retain memory unless that array is restored.
Read a JSON module without its cached valuereload-json-config
const config = stealthyRequire(require.cache, () => {
return require('./settings.json');
});This reloads CommonJS JSON modules, but direct `fs.readFile` plus `JSON.parse` is clearer and avoids clearing unrelated modules.
Create isolated module state in a testreset-module-state-test
it('starts from zero', () => {
const counter = stealthyRequire(require.cache, () => {
return require('../counter');
});
assert.equal(counter.next(), 1);
});Use the test runner's built-in module reset when available. This helper resets the complete synchronous dependency tree and can duplicate global side effects.
Keep a native addon outside the fresh graphprotect-native-addon
const nativeBinding = require('./build/Release/addon.node');
const freshWrapper = stealthyRequire(
require.cache,
() => require('./wrapper'),
() => require('./build/Release/addon.node'),
module,
);`.node` cache entries are never deleted by the implementation. The wrapper can be fresh, but the native binding remains the existing instance.
Use a child process for stronger isolationisolate-in-child-process
const { fork } = require('node:child_process');
const child = fork(require.resolve('./load-plugin-child'), [pluginPath], {
stdio: 'inherit',
});
child.on('exit', (code) => {
if (code !== 0) process.exitCode = code;
});This does not call stealthy-require because a new process has a clean module cache and contains native modules, globals, timers, and load failures more reliably.
Pass Browserify's legacy cache argumentbrowserify-cache-legacy
const fresh = stealthyRequire(arguments[5], () => {
return require('./feature');
});The README documents this for Browserify 13.0.1. It relies on generated wrapper internals and is not a portable modern bundler API.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| clear-module | npm | You want to evict one CommonJS module and its descendants explicitly instead of snapshotting the entire cache |
| import-fresh | npm | You want a small helper that returns a fresh CommonJS import for configuration or test code |
| decache | npm | You need a simple legacy API to remove a named module and its children from the CommonJS cache |