Back to BlogTesting

Jest 30.4 Finally Makes ESM Testing Less Annoying: 4 Upgrade Wins to Use Right Now

Jest 30.4 is one of those releases that looks small until you read the notes. The May 7, 2026 release, followed by patches on May 8 and May 9, quietly makes modern ESM setups less painful, adds first-class `jest.config.mts` support, fixes React 19 snapshot formatting, and gives fake timers real Temporal support.

JestESMNode.jsTemporalReact 19
Jest 30.4 Finally Makes ESM Testing Less Annoying: 4 Upgrade Wins to Use Right Now

Jest 30.4 is bigger than the version number makes it look

On May 7, 2026, Jest 30.4.0 landed. Then 30.4.1 showed up on May 8, and 30.4.2 followed on May 9. That alone tells you what kind of release this was: important, ambitious, and a little risky.

The maintainers literally called 30.4.0 a "Big release!" and said regressions might have slipped in. I actually like that level of honesty. It also means the smart move is obvious: do not pin to 30.4.0 just because that was the headline. Upgrade to 30.4.2 and get the fixes for the rough edges in the new runtime.

The real story here is ESM. Jest has spent years in that awkward zone where it could run modern code, but only after you memorized a pile of caveats, loader comments, flags, and tribal knowledge. Jest 30.4 does not erase all of that, but it is the first recent Jest release that feels like it is trying to meet Node where Node already is.

If you already use Vitest in greenfield Vite apps, fine. I get it. But if your team is already invested in Jest, especially in a mixed Node, React, and TypeScript codebase, this release is worth your attention.

1. jest.config.mts is the kind of boring feature that saves real time

This one is small on paper and excellent in practice.

The 30.4.0 release notes add support for jest.config.mts, and the current configuration docs now list jest.config.js|ts|mjs|mts|cjs|cts|json as valid auto-discovered config names.

That matters if your repo has already moved to ESM-first TypeScript and you were keeping a weird config exception around just for Jest.

Here is the clean version:

/** @jest-config-loader esbuild-register */
import {defineConfig} from 'jest';

export default defineConfig({
  testEnvironment: 'node',
  coverageProvider: 'v8',
  verbose: true,
});

This pairs nicely with defineConfig, which the docs now show as the default pattern for both JavaScript and TypeScript configs. If you split config across packages, mergeConfig is there too. That sounds cosmetic until you have a monorepo where every test package has been improvising its own config shape for a year.

My rule here is simple: if your app code already treats ESM as normal, your Jest config should stop acting like 2022 never ended.

2. The ESM runtime work is the real reason to care

The release notes are blunt about it: 30.4.0 includes a runtime rewrite "in preparation for stabilisation of native support of ESM." That is the headline.

The concrete wins are better than the headline:

  • require() of ES modules is now supported on Node 24.9+

  • Jest can load .js files with ESM syntax as native ESM even without a "type": "module" marker

  • Jest uses synchronous ESM evaluation on Node versions that support it

  • 30.4.1 and 30.4.2 immediately fixed CJS-from-ESM interop edge cases

That last part matters. This was not just a feature dump. The follow-up patches show the maintainers were watching the breakage reports and shipping fixes fast.

A very practical example is the old mixed-module annoyance where one part of the project still uses CommonJS but the thing you want to test is ESM:

// math.mjs
export function add(a, b) {
  return a + b;
}
// math.test.cjs
const {add} = require('./math.mjs');

test('adds numbers', () => {
  expect(add(2, 3)).toBe(5);
});

With 30.4, that path is much less cursed if you are on Node 24.9+. You still need --experimental-vm-modules, because the release notes explicitly say that requirement remains. But this is still progress. Real progress. Not marketing progress.

If your Jest setup currently depends on a bunch of defensive transforms just to stop ESM from blowing up, 30.4 is the version where I would start deleting some of that glue, one piece at a time.

3. workerGracefulExitTimeout is the sleeper CI fix

This feature did not get the flashy reaction, but I think a lot of teams will get more day-to-day value from it than from the ESM work.

Jest 30.4 adds workerGracefulExitTimeout, and the config docs now include it as a first-class option. If you have ever watched CI sit there doing nothing while a worker refuses to die, you already know why this matters.

import {defineConfig} from 'jest';

export default defineConfig({
  workerGracefulExitTimeout: 2000,
});

I would use this in repos that spin up local servers, background workers, message consumers, or browser-ish environments during tests. Not because it hides bugs. Because hanging shutdown is one of the dumbest ways to waste CI time.

You still need to fix the underlying cleanup if tests leak resources. But having Jest stop waiting forever is a good trade. Two seconds is usually enough to separate a graceful exit from a zombie process.

4. Temporal-aware fake timers are a nice bonus, and not just for nerds

Yes, this release also adds fake timer support for Temporal. Under normal circumstances I would call that a niche feature.

This is not normal circumstances.

Node 26 just pulled Temporal into everyday JavaScript conversation, and Jest moved quickly. The release notes say 30.4 accepts Temporal.Duration in advanceTimersByTime(), accepts Temporal.Instant and Temporal.ZonedDateTime in setSystemTime() and useFakeTimers({ now }), and can fake Temporal.Now.*.

That means test code like this stops feeling experimental:

import {jest} from '@jest/globals';

jest.useFakeTimers({
  now: Temporal.ZonedDateTime.from('2026-05-14T10:00:00+05:30[Asia/Calcutta]'),
});

test('expires after 30 minutes', () => {
  jest.advanceTimersByTime(Temporal.Duration.from({minutes: 30}));
  expect(isExpired()).toBe(true);
});

If your codebase is starting to migrate off Date, this is a real testing upgrade, not just a neat bullet point.

My take

Jest 30.4 is not a "switch everything today" release. It is a "finally, this is moving in the right direction" release.

If you are already on Jest, I would absolutely move to 30.4.2 and test three things first:

  • your ESM interop paths on Node 24.9+

  • your config cleanup, especially if you wanted jest.config.mts

  • your flaky CI shutdown behavior with workerGracefulExitTimeout

The React 19 pretty-format support is another easy win if you snapshot components and were seeing weird output. That fix alone will make some frontend teams happier than the runtime rewrite.

Vitest is still the easier sell for brand-new Vite projects. I do not think Jest suddenly stole that crown back. But for big existing repos, mixed module systems, React apps, and test stacks that cannot just rip and replace, Jest 30.4 feels like the first release in a while that gives you fewer reasons to apologize for still using Jest.

That is more than enough to make it worth a weekend upgrade branch.