When does Jest's transform cache serve stale output from a custom transformer that reads external files?
0 reputation · 13 Aug 2025, 20:47 UTC
0 reputation · 13 Aug 2025, 20:47 UTC
I'm designing a custom Jest transformer (Jest 29) that inlines values from a shared config file at transform time. My understanding is that Jest's on-disk transform cache keys entries by the source file's content hash, the transformer's own code, the Jest version, and parts of the config — but not by arbitrary files the transformer happens to read.
That suggests editing only the external config file would leave the old transformed output in place, since nothing in the cache key changes. I want to confirm exactly which inputs participate in the key, and what the supported mechanism is for extending it (for example, getCacheKey on a custom transformer) versus relying on jest --clearCache in CI.
I'm also unsure how this interacts with the jest-haste-map cache, which is a separate layer tracking the module map rather than transformed code.
Specifically:
getCacheKey (hashing the external file's contents) the documented way to invalidate on external-state changes, or is there a config-level alternative?31775 reputation · 13 Aug 2025, 21:08 UTC
Jest’s on‑disk transform cache will serve stale output whenever a custom transformer reads an external file (e.g., a shared config) but does not include that file’s content in the cache key. If the external file changes and the transformer’s getCacheKey method is not implemented (or does not hash the file), the cache key remains unchanged, so Jest returns the previously transformed result.
process method are not automatically hashed into the default key.getCacheKey method that returns a hash of any external dependency is the documented way to extend the cache key and force a re‑transform when that dependency changes.When you edit only the external config file, the source file’s hash, the transformer code, and the Jest config stay the same. Because the default key ignores the config file, the cache entry remains valid and Jest returns the old transformed output. This manifests as stale inlined values until the cache is busted (e.g., by jest --clearCache) or the key is changed via getCacheKey.
getCacheKey in your custom transformer:const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
module.exports = {
process(src, filepath, config, options) {
// your inlining logic that reads e.g. path.join(filepath, '..', 'shared.json')
return transformed;
},
getCacheKey(fileData, fsStr, options, instrument) {
// hash the transformer source (fileData) as Jest does by default
const base = crypto.createHash('md5').update(fileData, 'utf8').digest('hex');
// hash the external config file you read
const configPath = path.resolve(options.rootDir, 'shared.json');
const configHash = crypto.createHash('md5')
.update(fsStr.readFileSync(configPath, 'utf8'), 'utf8')
.digest('hex');
// combine with Jest version and config if desired
return `${base}-${configHash}-${options.jestVersion}`;
}
};
--clearCache.jest --clearCache before each build; this clears both transform and haste‑map caches.Editing or copying files into a CI workspace can leave the haste‑map with stale module‑resolution entries, causing Jest to look for a module at the old path. This is independent of the transform cache. Enabling Watchman (or setting watchman: true in Jest config) makes the haste‑map update faster, but it does not fix stale transform output caused by external file changes; you still need a proper getCacheKey or a cache clear.
Are you observing the stale output only when running Jest in watch mode, or also in a fresh CI run? Knowing this will determine whether enabling Watchman (for haste‑map freshness) is sufficient or whether a cache‑clear step is required.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.