Skip to content

fix(@jest/transform): skip the code cache write when cache is disabled - #16464

Open
tony-scio wants to merge 2 commits into
jestjs:mainfrom
tony-scio:fix/transform-skip-code-cache-write-without-cache
Open

tony-scio wants to merge 2 commits into
jestjs:mainfrom
tony-scio:fix/transform-skip-code-cache-write-without-cache

Conversation

@tony-scio

Copy link
Copy Markdown

Summary

ScriptTransformer writes the transformed code to cacheDirectory for every module it transforms, even when cache is false (--no-cache). That file is only ever read back behind this._config.cache (both readCodeCacheFile call sites), so with the cache off, each write is thrown away. Skipping it avoids one atomic write per transformed module: a temp-file write plus a rename, from write-file-atomic.

This change guards the single writeCodeCacheFile call with this._config.cache. Nothing else changes:

  • Source maps are still written. sourceMapPath is returned to callers and read back by path for stack-trace remapping and coverage, regardless of cache.
  • cache: true (the default) behaves exactly as before. Same reads, same writes, same keys.

Why it matters

The general point: writing a file that will never be read is pure overhead. It costs filesystem syscalls, disk I/O and cache-directory churn, and grows with the number of modules a test run transforms.

It matters more in sandboxed or hermetic runners, where cache: false is the norm rather than an escape hatch. For example, aspect_rules_jest for Bazel sets config.cache = false and points cacheDirectory at the test's fresh TEST_TMPDIR, because a transform cache cannot be shared across hermetic test actions. There, every Jest shard pays for one throwaway write per transformed module on every run. Sandbox filesystems (symlink trees, FUSE, network-backed temp dirs) tend to make those small writes and renames more expensive than on a plain local disk.

Benchmarks

All numbers are wall-clock medians of interleaved runs. The order alternates each round, the same checkout is used for both sides, and only this change differs (@jest/transform/build swapped between main and this branch). macOS, Apple M-series, Node 26.

Minimal repro (1,531 small ES modules, 8 test files, babel-jest, jest --no-cache with a fresh --cacheDirectory, 6 runs per side):

main this PR
--runInBand 3.19s (3.16–3.25) 2.72s (2.66–2.91) −14.6%
--maxWorkers=4 5.30s (5.26–5.40) 4.33s (4.23–4.40) −18.3%
code-cache files written 1,543 0
source-map files written 1,543 1,543 unchanged

A large private repo (a 403-test slice, @swc/jest, 5 runs per side):

main this PR
jest --no-cache, fresh cache dir, 3 workers 11.36s (11.24–12.33) 10.15s (10.07–10.22) −10.7%
same slice under Bazel (aspect_rules_jest, sandboxed, cache: false), Jest Time: 11.43s (11.37–11.48) 10.19s (10.15–10.35) −10.8%
code-cache files written 1,854 0

In every configuration, this PR was faster in every interleaved pair, and the min/max ranges do not overlap. Test results were identical.

Minimal repro setup
// generate.js: 1,500 leaf modules, 30 barrels of 50, one index, 8 tests importing the index
const fs = require('fs');
fs.mkdirSync('src', {recursive: true});
fs.mkdirSync('__tests__', {recursive: true});
const N = 1500, G = 50;
for (let i = 0; i < N; i++)
  fs.writeFileSync(`src/m${i}.js`, `export const v${i} = x => [1, 2, 3].map(n => n * x).reduce((s, n) => s + n, 0) + ${i};\n`);
const groups = [];
for (let g = 0; g < N / G; g++) {
  const ids = Array.from({length: G}, (_, k) => g * G + k);
  fs.writeFileSync(`src/g${g}.js`, ids.map(i => `import {v${i}} from './m${i}';\n`).join('') +
    `export const sum${g} = x => ${ids.map(i => `v${i}(x)`).join(' + ')};\n`);
  groups.push(g);
}
fs.writeFileSync('src/index.js', groups.map(g => `import {sum${g}} from './g${g}';\n`).join('') +
  `export const total = x => ${groups.map(g => `sum${g}(x)`).join(' + ')};\n`);
for (let t = 0; t < 8; t++)
  fs.writeFileSync(`__tests__/t${t}.test.js`, `import {total} from '../src/index';\ntest('ok', () => { expect(total(1)).toBeGreaterThan(0); });\n`);

With babel-jest + @babel/plugin-transform-modules-commonjs (sourceMaps: 'both'), run rm -rf cc && jest --no-cache --cacheDirectory=cc, then count the files under cc/jest-transform-cache-* (*.map vs the rest).

Test plan

  • Updated the three "reads values from the cache" tests (sync, async, async preprocessor). They already asserted that the code cache is not read with cache: false; they now also assert that it is not written (writeFileAtomic.sync not called with the code-cache path), where before they asserted that some write happened.
  • Added writes only the source map when \config.cache` is false`: with a transformer that returns a map, exactly one write happens, and it is the source map.
  • Reverting the source change makes those four tests fail. With it, all tests in packages/jest-transform pass:
$ FORCE_COLOR=1 yarn jest packages/jest-transform
Tests:       101 passed, 101 total
Snapshots:   33 passed, 33 total
  • Every e2e suite that runs with --no-cache, plus coverageRemapping, stackTrace, transform and clearCache (29 suites; this covers V8/babel coverage and source-mapped stack traces under --no-cache):
$ yarn build:js && FORCE_COLOR=1 yarn jest $(rg -l "'--no-cache'" e2e/__tests__) e2e/__tests__/{coverageRemapping,stackTrace,transform,clearCache}.test.ts
Test Suites: 29 passed, 29 total
Tests:       119 passed, 119 total
Snapshots:   74 passed, 74 total
  • yarn build, yarn typecheck:tests, yarn eslint and yarn prettier --check on the changed files, and yarn check-copyright-headers: all pass.

— sent via Glean Tau

The transformed-code cache file is only ever read back when `config.cache` is
true, so writing it under `--no-cache` / `cache: false` costs an atomic
write (write + rename) per transformed module for nothing. Source maps are
still written: stack-trace remapping and coverage read them back by path.
@netlify

netlify Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for jestjs ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 4ebfa45
🔍 Latest deploy log https://app.netlify.com/projects/jestjs/deploys/6ab7283e1edc2800085f2672
😎 Deploy Preview https://deploy-preview-16464--jestjs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@linux-foundation-easycla

linux-foundation-easycla Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

@github-actions github-actions Bot added the require-changelog If a PR does requires a changelog entry label Sep 26, 2026
@pkg-pr-new

pkg-pr-new Bot commented Sep 26, 2026

Copy link
Copy Markdown

Open in StackBlitz

babel-jest

npm i https://pkg.pr.new/babel-jest@16464

babel-plugin-jest-hoist

npm i https://pkg.pr.new/babel-plugin-jest-hoist@16464

babel-preset-jest

npm i https://pkg.pr.new/babel-preset-jest@16464

create-jest

npm i https://pkg.pr.new/create-jest@16464

@jest/diff-sequences

npm i https://pkg.pr.new/@jest/diff-sequences@16464

expect

npm i https://pkg.pr.new/expect@16464

@jest/expect-utils

npm i https://pkg.pr.new/@jest/expect-utils@16464

jest

npm i https://pkg.pr.new/jest@16464

jest-changed-files

npm i https://pkg.pr.new/jest-changed-files@16464

jest-circus

npm i https://pkg.pr.new/jest-circus@16464

jest-cli

npm i https://pkg.pr.new/jest-cli@16464

jest-config

npm i https://pkg.pr.new/jest-config@16464

@jest/console

npm i https://pkg.pr.new/@jest/console@16464

@jest/core

npm i https://pkg.pr.new/@jest/core@16464

@jest/create-cache-key-function

npm i https://pkg.pr.new/@jest/create-cache-key-function@16464

jest-diff

npm i https://pkg.pr.new/jest-diff@16464

jest-docblock

npm i https://pkg.pr.new/jest-docblock@16464

jest-each

npm i https://pkg.pr.new/jest-each@16464

@jest/environment

npm i https://pkg.pr.new/@jest/environment@16464

jest-environment-jsdom

npm i https://pkg.pr.new/jest-environment-jsdom@16464

@jest/environment-jsdom-abstract

npm i https://pkg.pr.new/@jest/environment-jsdom-abstract@16464

jest-environment-node

npm i https://pkg.pr.new/jest-environment-node@16464

@jest/expect

npm i https://pkg.pr.new/@jest/expect@16464

@jest/fake-timers

npm i https://pkg.pr.new/@jest/fake-timers@16464

@jest/get-type

npm i https://pkg.pr.new/@jest/get-type@16464

@jest/globals

npm i https://pkg.pr.new/@jest/globals@16464

jest-haste-map

npm i https://pkg.pr.new/jest-haste-map@16464

jest-jasmine2

npm i https://pkg.pr.new/jest-jasmine2@16464

jest-leak-detector

npm i https://pkg.pr.new/jest-leak-detector@16464

jest-matcher-utils

npm i https://pkg.pr.new/jest-matcher-utils@16464

jest-message-util

npm i https://pkg.pr.new/jest-message-util@16464

jest-mock

npm i https://pkg.pr.new/jest-mock@16464

@jest/pattern

npm i https://pkg.pr.new/@jest/pattern@16464

jest-phabricator

npm i https://pkg.pr.new/jest-phabricator@16464

jest-regex-util

npm i https://pkg.pr.new/jest-regex-util@16464

@jest/reporters

npm i https://pkg.pr.new/@jest/reporters@16464

jest-resolve

npm i https://pkg.pr.new/jest-resolve@16464

jest-resolve-dependencies

npm i https://pkg.pr.new/jest-resolve-dependencies@16464

jest-runner

npm i https://pkg.pr.new/jest-runner@16464

jest-runtime

npm i https://pkg.pr.new/jest-runtime@16464

@jest/schemas

npm i https://pkg.pr.new/@jest/schemas@16464

jest-snapshot

npm i https://pkg.pr.new/jest-snapshot@16464

@jest/snapshot-utils

npm i https://pkg.pr.new/@jest/snapshot-utils@16464

@jest/source-map

npm i https://pkg.pr.new/@jest/source-map@16464

@jest/test-result

npm i https://pkg.pr.new/@jest/test-result@16464

@jest/test-sequencer

npm i https://pkg.pr.new/@jest/test-sequencer@16464

@jest/transform

npm i https://pkg.pr.new/@jest/transform@16464

@jest/types

npm i https://pkg.pr.new/@jest/types@16464

jest-util

npm i https://pkg.pr.new/jest-util@16464

jest-validate

npm i https://pkg.pr.new/jest-validate@16464

jest-watcher

npm i https://pkg.pr.new/jest-watcher@16464

jest-worker

npm i https://pkg.pr.new/jest-worker@16464

pretty-format

npm i https://pkg.pr.new/pretty-format@16464

commit: 4ebfa45

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

require-changelog If a PR does requires a changelog entry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant