Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Better ESM-CJS compatibility fix#16050

Merged
SimenB merged 31 commits into
jestjs:mainfrom
jmuransky:main
Apr 30, 2026
Merged

Better ESM-CJS compatibility fix#16050
SimenB merged 31 commits into
jestjs:mainfrom
jmuransky:main

Conversation

@jmuransky
Copy link
Copy Markdown
Contributor

Helps solve the issue from #15275

The idea behind it -> ESM modules need to be compiled in async mode while CJS needs to be compiled in sync mode. To get to this state, we need to basically resolve all module dependencies before we even start to compile.

The only way I could think of how to get to this state is to - open the file, walk thru it via AST, pull all imports and create import tree (graph) from it. Leaf imports do not depend on anything, so we can directly import them. Then we can walk up the tree (graph) which in the end handles all the imports while keeping CJS imports synchronous (they see compiled module in cache).

Summary

Motivation for this was, that my project - a mixture of ESM+CJS modules that imported each other - was crashing. This solves the issue at least in my project.

Test plan

I added test that spawns new jest runner that actually handles imports (and exits successfully).

Disclosure

I used AI when writing this - but it should not be visible. I verified every line of code. I asked very specific tasks out of it instead of "solve this". The algorithm/idea is mine - AI just wrote it and helped me debug issues (and write tests). But I know precisely what every line does and why it's there (and that it can't be removed).

Note

Feel free to edit anything/everything from this pull request. I expect some parts could be optimized for better performance.

It might be possible, that there's better AST parser for this (or there's entirely different solution for this) and I am fine with using that.
One thing that you might also consider - given my solution, each file should get resolved at least 2x (once for preloading dependencies and once for actual transformation). This probably could be cached (file reading actually is cached).

On the flip side - this solution should not slow jest a lot if the project does not import cjs-esm-cjs too much.

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 22, 2026

Deploy Preview for jestjs ready!

Name Link
🔨 Latest commit cee3085
🔍 Latest deploy log https://app.netlify.com/projects/jestjs/deploys/69f312b21b81370008a5b771
😎 Deploy Preview https://deploy-preview-16050--jestjs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

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

@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla Bot commented Apr 22, 2026

CLA Signed

The committers listed above are authorized under a signed CLA.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 22, 2026

Open in StackBlitz

babel-jest

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

babel-plugin-jest-hoist

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

babel-preset-jest

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

create-jest

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

@jest/diff-sequences

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

expect

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

@jest/expect-utils

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

jest

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

jest-changed-files

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

jest-circus

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

jest-cli

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

jest-config

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

@jest/console

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

@jest/core

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

@jest/create-cache-key-function

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

jest-diff

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

jest-docblock

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

jest-each

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

@jest/environment

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

jest-environment-jsdom

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

@jest/environment-jsdom-abstract

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

jest-environment-node

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

@jest/expect

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

@jest/fake-timers

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

@jest/get-type

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

@jest/globals

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

jest-haste-map

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

jest-jasmine2

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

jest-leak-detector

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

jest-matcher-utils

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

jest-message-util

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

jest-mock

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

@jest/pattern

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

jest-phabricator

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

jest-regex-util

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

@jest/reporters

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

jest-resolve

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

jest-resolve-dependencies

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

jest-runner

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

jest-runtime

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

@jest/schemas

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

jest-snapshot

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

@jest/snapshot-utils

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

@jest/source-map

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

@jest/test-result

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

@jest/test-sequencer

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

@jest/transform

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

@jest/types

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

jest-util

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

jest-validate

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

jest-watcher

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

jest-worker

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

pretty-format

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

commit: cee3085

@jmuransky jmuransky marked this pull request as draft April 22, 2026 12:30
@jmuransky jmuransky marked this pull request as ready for review April 23, 2026 15:41
@jmuransky
Copy link
Copy Markdown
Contributor Author

jmuransky commented Apr 23, 2026

I don't know how to fix these last 2 checks without changing global jest project's unit test configuration (am I allowed to do that?).
My whole functionality is about ESM/CJS interaction so there's no reasonable way to write unit tests to raise coverage, that tests something I wrote and while not having --experimental-vm-modules turned on.

@jmuransky jmuransky marked this pull request as draft April 24, 2026 13:11
@SimenB
Copy link
Copy Markdown
Member

SimenB commented Apr 24, 2026

Thanks! I've been thinking of picking up require(esm) in the last couple of days due to node upstream picking up the ESM vm APIs again (nodejs/node#62720 for reference ). Will make sure to look through this at the same time 🙂 maaaaybe over the weekend, but I don't wanna make any promises 😅

@SimenB
Copy link
Copy Markdown
Member

SimenB commented Apr 29, 2026

If you wanna review it, I'd very much welcome it! 🙂 but don't feel obliged. I'd rather you first change this PR so its bug fixes can land 👍

@jmuransky
Copy link
Copy Markdown
Contributor Author

It's quite late in here, so I'll need to look at this better tomorrow.

I tried the thing out, but from what I can tell right now - the linking step is always async. That would mean cjs (still needing to be synchronous) importing esm will always fail (no way to wait) if there's no way to inspect it's content ahead of time.
There are a few tests that are failing me on this thing. I'll try to figure out something tomorrow

@SimenB
Copy link
Copy Markdown
Member

SimenB commented Apr 30, 2026

Linking step is async ATM yes - that sync code path will land in #16063. After that the plan is to add require(esm) as a follow-up.

But the fixes I was thinking could land from this PR aren't on the require(esm) path - they're on the existing module.link(asyncLinker) flow. Sync linking and require(esm) come later and pick up your loadCjsAsEsm fixes for free (the sync flow calls the same _buildCjsAsEsmSyntheticModule helper).

So bug fixes can land here independently of #16063 (and its yet-to-be-wrriten follow-up) - don't worry about sync linking. The failing test looks to be from e2e/native-esm-cjs-require/cjs-with-esm-req.cjs? that won't work until later and can be removed for now.

@SimenB
Copy link
Copy Markdown
Member

SimenB commented Apr 30, 2026

I pushed to do a quick cleanup pass (removing the failing test as it won't work yet, removed the no longer needed deps, and added a CI run). Let's see what CI says 😀

@jmuransky
Copy link
Copy Markdown
Contributor Author

Perfect. Thanks

Copy link
Copy Markdown
Member

@SimenB SimenB left a comment

Choose a reason for hiding this comment

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

Thanks for taking a deep dive into the weeds of our ESM implementation 😀 I'll try to land require(esm) in the next few days and make a release 🙂

@jmuransky
Copy link
Copy Markdown
Contributor Author

I am really looking forward to it.
I still wonder how you'll fix cjs project requiring esm too. If you'd remember me after doing PR. Can you ping me so I can see it please?

@SimenB
Copy link
Copy Markdown
Member

SimenB commented Apr 30, 2026

Yeah, for sure 🙂

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates jest-runtime’s VM-module loading flow to improve ESM↔CJS interoperability (notably around importing CJS from ESM, SyntaxError fallback to native ESM parsing, and concurrency/deadlock behavior), and adds fixtures/tests plus CI coverage to validate the new behavior under --experimental-vm-modules.

Changes:

  • Update jest-runtime module linking/evaluation logic to better handle concurrency and avoid deadlocks during linking/importing (including wasm import wiring).
  • Improve CJS-as-ESM interop: cache the wrapper module, merge static+runtime named exports, hide __esModule, and unwrap Babel-style default exports; add SyntaxError fallback to load “ESM-in-.js-without-type-module” as native ESM.
  • Add unit/e2e fixtures and a dedicated CI job/script to run jest-runtime tests with --experimental-vm-modules; add changelog entries.

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/jest-runtime/tsconfig.json Adds ESNext.Error lib for Error.isError typing.
packages/jest-runtime/src/index.ts Implements updated link/evaluate flow, CJS-as-ESM caching/interop tweaks, SyntaxError fallback, wasm deadlock avoidance.
packages/jest-runtime/src/tests/runtime_esm_cjs_interop.test.ts Adds runtime-level tests for CJS-as-ESM behavior (gated on VM modules support).
packages/jest-runtime/src/tests/test_esm_interop_root/* Adds fixture modules to exercise the new interop paths.
package.json Adds scripts to run jest-runtime tests with --experimental-vm-modules.
jest.config.mjs Ignores the new runtime fixture directory at repo root test config level.
e2e/native-esm-cjs-require/* Adds an E2E fixture covering default unwrapping, named exports, and singleton caching.
e2e/tests/nativeEsmCjsRequire.test.ts Adds an E2E test runner for the new fixture.
e2e/tests/snapshots/nativeEsmCjsRequire.test.ts.snap Snapshot for the new E2E test’s summary output.
CHANGELOG.md Adds entries describing the interop fixes.
.github/workflows/nodejs.yml Adds a CI job to run jest-runtime tests with --experimental-vm-modules.

Comment on lines 851 to +856
private loadCjsAsEsm(from: string, modulePath: string, context: VMContext) {
const registry = this._isolatedModuleRegistry ?? this._esmoduleRegistry;
const cached = registry.get(modulePath);
if (cached) {
return cached as SyntheticModule;
}
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

loadCjsAsEsm caches the newly created SyntheticModule in registry before evaluateSyntheticModule() completes, but the cached path returns the module synchronously. If a second importer hits the cache while the first call is still linking/evaluating, Node may attempt to link the same SyntheticModule twice (or consumers may observe it before evaluation completes). Consider caching the promise for link/evaluate (the registry supports Promise<ESModule>), and always returning/awaiting that shared promise (or only caching after the module is fully linked/evaluated).

Copilot uses AI. Check for mistakes.
Comment on lines +893 to +896
const allCandidates = new Set([
...parsedExports,
...Object.keys(cjs as Record<string, unknown>),
]);
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

Object.keys(cjs as Record<string, unknown>) will throw if a CJS module sets module.exports = null (or undefined). Since CJS exports can legally be null/primitive, this path should avoid calling Object.keys / hasOwnProperty on non-objects (e.g. use Object.keys(Object(cjs)) or guard with a typeof/null check and fall back to the static export set).

Copilot uses AI. Check for mistakes.
Comment on lines +828 to +829
await this._esmModuleEvaluatingMap.get(module);

Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

linkAndEvaluateModule only stores an entry in _esmModuleEvaluatingMap when it calls module.evaluate() itself. If linkAndEvaluateModule is invoked while the module is already in 'evaluating' state due to Node's evaluation cascade, there may be no cached promise and the final await this._esmModuleEvaluatingMap.get(module) becomes a no-op, returning while the module is still evaluating. Consider explicitly handling module.status === 'evaluating' (and no stored promise) by waiting until the status changes, or by ensuring an evaluation promise is recorded for all evaluation entry points.

Suggested change
await this._esmModuleEvaluatingMap.get(module);
let evalPromise = this._esmModuleEvaluatingMap.get(module);
if (evalPromise == null && module.status === 'evaluating') {
evalPromise = new Promise<void>((resolve, reject) => {
const waitForEvaluation = () => {
const status = module.status as VMModule['status'];
if (status === 'evaluated') {
resolve();
return;
}
if (status === 'errored') {
reject(module.error);
return;
}
if (status === 'evaluating') {
queueMicrotask(waitForEvaluation);
return;
}
reject(
new Error(
`Module ${module.identifier} changed to unexpected status "${status}" while evaluating.`,
),
);
};
waitForEvaluation();
});
this._esmModuleEvaluatingMap.set(module, evalPromise);
}
await evalPromise;

Copilot uses AI. Check for mistakes.
@SimenB SimenB merged commit 1141bd4 into jestjs:main Apr 30, 2026
73 of 75 checks passed
@jmuransky
Copy link
Copy Markdown
Contributor Author

Cool :)

I'll try to check the support more over these next 3 days as current version does not run with my esm/cjs riddled project unfortunately. Might be I'll be able to help with your implementation a little

@SimenB
Copy link
Copy Markdown
Member

SimenB commented May 1, 2026

See #16074 🙂

@SimenB
Copy link
Copy Markdown
Member

SimenB commented May 7, 2026

renovate Bot added a commit to andrei-picus-tink/auto-renovate that referenced this pull request May 8, 2026
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| npm        | jest    | 30.3.0 | 30.4.0 |


## [v30.4.0](https://github.com/jestjs/jest/blob/HEAD/CHANGELOG.md#3040)

##### Features

- `[babel-jest]` Support collecting coverage from `.mts`, `.cts` (and other) files ([#15994](jestjs/jest#15994))
- `[jest-circus, jest-cli, jest-config, jest-core, jest-jasmine2, jest-types]` Add `--collect-tests` flag to discover and list tests without executing them ([#16006](jestjs/jest#16006))
- `[jest-config, jest-runner, jest-worker]` Add `workerGracefulExitTimeout` config option to control how long workers are given to exit before being force-killed ([#15984](jestjs/jest#15984))
- `[jest-config]` Add support for `jest.config.mts` as a valid configuration file ([#16005](jestjs/jest#16005))
- `[jest-config, jest-core, jest-reporters, jest-runner]` `verbose` and `silent` can now be set per-project; the project-level value overrides the global value for that project's tests ([#16133](jestjs/jest#16133))
- `[@jest/fake-timers]` Accept `Temporal.Duration` in `jest.advanceTimersByTime()` and `jest.advanceTimersByTimeAsync()` ([#16128](jestjs/jest#16128))
- `[@jest/fake-timers]` Accept `Temporal.Instant` and `Temporal.ZonedDateTime` in `jest.setSystemTime()` and `useFakeTimers({now})` ([#16128](jestjs/jest#16128))
- `[@jest/fake-timers]` Support faking `Temporal.Now.*` ([#16131](jestjs/jest#16131))
- `[jest-mock]` Add `clearMocksOnScope(scope)` on `ModuleMocker` for clearing every mock function exposed on a scope object ([#16088](jestjs/jest#16088))
- `[jest-resolve]` Add `canResolveSync()` on `Resolver` so callers can detect when a user-configured resolver only exports an `async` hook ([#16064](jestjs/jest#16064))
- `[jest-runtime]` Use synchronous `evaluate()` for ES modules without top-level `await` on Node versions that support it (v24.9+), and prefer the synchronous transform path when a sync transformer is configured ([#16062](jestjs/jest#16062))
- `[jest-runtime]` Support `require()` of ES modules on Node v24.9+ ([#16074](jestjs/jest#16074))
- `[jest-runtime]` Validate TC39 import attributes (`with { type: 'json' }`) on ESM imports ([#16127](jestjs/jest#16127))
- `[@jest/transform]` Add `canTransformSync(filename)` on `ScriptTransformer` so callers can pick the sync vs async transform path ([#16062](jestjs/jest#16062))
- `[jest-util]` Add `isError` helper ([#16076](jestjs/jest#16076))
- `[pretty-format]` Support React 19 ([#16123](jestjs/jest#16123))

##### Fixes

- `[expect-utils]` Fix `toStrictEqual` failing on `structuredClone` results due to cross-realm constructor mismatch ([#15959](jestjs/jest#15959))
- `[@jest/expect-utils]` Prevent `toMatchObject`/subset matching from throwing when encountering exotic iterables ([#15952](jestjs/jest#15952))
- `[fake-timers]` Convert `Date` to milliseconds before passing to `@sinonjs/fake-timers` ([#16029](jestjs/jest#16029))
- `[jest]` Export `GlobalConfig` and `ProjectConfig` TypeScript types ([#16132](jestjs/jest#16132))
- `[jest-circus]` Prevent crash when `asyncError` is undefined for non-Error throws ([#16003](jestjs/jest#16003))
- `[jest-circus, jest-jasmine2]` Include `Error.cause` in JSON `failureMessages` output ([#15967](jestjs/jest#15967))
- `[jest-config]` Fix preset path resolution on Windows when the preset uses subpath `exports` ([#15961](jestjs/jest#15961))
- `[jest-config]` Allow `collectCoverage` and `coverageProvider` in project config without a validation warning ([#16132](jestjs/jest#16132))
- `[jest-config]` Project config validator now emits "is not supported in an individual project configuration" instead of "probably a typing mistake" for known global-only options ([#16132](jestjs/jest#16132))
- `[jest-environment-node]` Fix `--localstorage-file` warning on Node 25+ ([#16086](jestjs/jest#16086))
- `[jest-reporters]` Apply global coverage threshold to unmatched pattern files in addition to glob/path thresholds ([#16137](jestjs/jest#16137))
- `[jest-reporters, jest-runner, jest-runtime, jest-transform]` Fix coverage report not showing correct code coverage when using `projects` config option ([#16140](jestjs/jest#16140))
- `[jest-runtime]` Resolve `expect` and `@jest/expect` from the internal module registry so test-file imports share the same `JestAssertionError` as the global `expect` ([#16130](jestjs/jest#16130))
- `[jest-runtime]` Improve CJS-from-ESM interop: `__esModule`/Babel default unwrap, broader named-export coverage, and shared CJS singleton across importers ([#16050](jestjs/jest#16050))
- `[jest-runtime]` Load `.js` files with ESM syntax but no `"type":"module"` marker as native ESM ([#16050](jestjs/jest#16050))
- `[jest-runtime]` Extend the `.js`-with-ESM-syntax fallback to `require()` on Node v24.9+ - falls back to `require(esm)` when the CJS parser rejects ESM syntax ([#16078](jestjs/jest#16078))
- `[jest-runtime]` Fix deadlocks and double-evaluation in concurrent ESM and wasm imports ([#16050](jestjs/jest#16050))
- `[jest-runtime]` Fix error when `require()` is called after the Jest environment has been torn down ([#15951](jestjs/jest#15951))
- `[jest-runtime]` Fix missing error when `import()` is called after the Jest environment has been torn down ([#16080](jestjs/jest#16080))
- `[jest-runtime]` Fix virtual `unstable_mockModule` registrations not respected in ESM ([#16081](jestjs/jest#16081))
- `[jest-runtime]` Apply `moduleNameMapper` when resolving modules with `require.resolve()` and the `paths` option ([#16135](jestjs/jest#16135))

##### Chore & Maintenance

- `[@jest/fake-timers]` Upgrade `@sinonjs/fake-timers` ([#16139](jestjs/jest#16139))
- `[jest-runtime]` Use synchronous `linkRequests` / `instantiate` for ESM linking on Node v24.9+ ([#16063](jestjs/jest#16063))
renovate Bot added a commit to andrei-picus-tink/auto-renovate that referenced this pull request May 10, 2026
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| npm        | jest    | 30.3.0 | 30.4.2 |


## [v30.4.2](https://github.com/jestjs/jest/blob/HEAD/CHANGELOG.md#3042)

##### Fixes

- `[jest-runtime]` Fix named imports from CJS modules whose `module.exports` is a function with own-property exports ([#16150](jestjs/jest#16150))


## [v30.4.1](https://github.com/jestjs/jest/blob/HEAD/CHANGELOG.md#3041)

##### Features

- `[jest-config, jest-core, jest-runner, jest-schemas, jest-types]` Allow custom runner configuration options via tuple format `['runner-path', {options}]` ([#16141](jestjs/jest#16141))

##### Fixes

- `[jest-runtime]` Align CJS-from-ESM default export with Node: `module.exports` is always the ESM default, `__esModule` unwrapping is no longer applied ([#16143](jestjs/jest#16143))


## [v30.4.0](https://github.com/jestjs/jest/blob/HEAD/CHANGELOG.md#3040)

##### Features

- `[babel-jest]` Support collecting coverage from `.mts`, `.cts` (and other) files ([#15994](jestjs/jest#15994))
- `[jest-circus, jest-cli, jest-config, jest-core, jest-jasmine2, jest-types]` Add `--collect-tests` flag to discover and list tests without executing them ([#16006](jestjs/jest#16006))
- `[jest-config, jest-runner, jest-worker]` Add `workerGracefulExitTimeout` config option to control how long workers are given to exit before being force-killed ([#15984](jestjs/jest#15984))
- `[jest-config]` Add support for `jest.config.mts` as a valid configuration file ([#16005](jestjs/jest#16005))
- `[jest-config, jest-core, jest-reporters, jest-runner]` `verbose` and `silent` can now be set per-project; the project-level value overrides the global value for that project's tests ([#16133](jestjs/jest#16133))
- `[@jest/fake-timers]` Accept `Temporal.Duration` in `jest.advanceTimersByTime()` and `jest.advanceTimersByTimeAsync()` ([#16128](jestjs/jest#16128))
- `[@jest/fake-timers]` Accept `Temporal.Instant` and `Temporal.ZonedDateTime` in `jest.setSystemTime()` and `useFakeTimers({now})` ([#16128](jestjs/jest#16128))
- `[@jest/fake-timers]` Support faking `Temporal.Now.*` ([#16131](jestjs/jest#16131))
- `[jest-mock]` Add `clearMocksOnScope(scope)` on `ModuleMocker` for clearing every mock function exposed on a scope object ([#16088](jestjs/jest#16088))
- `[jest-resolve]` Add `canResolveSync()` on `Resolver` so callers can detect when a user-configured resolver only exports an `async` hook ([#16064](jestjs/jest#16064))
- `[jest-runtime]` Use synchronous `evaluate()` for ES modules without top-level `await` on Node versions that support it (v24.9+), and prefer the synchronous transform path when a sync transformer is configured ([#16062](jestjs/jest#16062))
- `[jest-runtime]` Support `require()` of ES modules on Node v24.9+ ([#16074](jestjs/jest#16074))
- `[jest-runtime]` Validate TC39 import attributes (`with { type: 'json' }`) on ESM imports ([#16127](jestjs/jest#16127))
- `[@jest/transform]` Add `canTransformSync(filename)` on `ScriptTransformer` so callers can pick the sync vs async transform path ([#16062](jestjs/jest#16062))
- `[jest-util]` Add `isError` helper ([#16076](jestjs/jest#16076))
- `[pretty-format]` Support React 19 ([#16123](jestjs/jest#16123))

##### Fixes

- `[expect-utils]` Fix `toStrictEqual` failing on `structuredClone` results due to cross-realm constructor mismatch ([#15959](jestjs/jest#15959))
- `[@jest/expect-utils]` Prevent `toMatchObject`/subset matching from throwing when encountering exotic iterables ([#15952](jestjs/jest#15952))
- `[fake-timers]` Convert `Date` to milliseconds before passing to `@sinonjs/fake-timers` ([#16029](jestjs/jest#16029))
- `[jest]` Export `GlobalConfig` and `ProjectConfig` TypeScript types ([#16132](jestjs/jest#16132))
- `[jest-circus]` Prevent crash when `asyncError` is undefined for non-Error throws ([#16003](jestjs/jest#16003))
- `[jest-circus, jest-jasmine2]` Include `Error.cause` in JSON `failureMessages` output ([#15967](jestjs/jest#15967))
- `[jest-config]` Fix preset path resolution on Windows when the preset uses subpath `exports` ([#15961](jestjs/jest#15961))
- `[jest-config]` Allow `collectCoverage` and `coverageProvider` in project config without a validation warning ([#16132](jestjs/jest#16132))
- `[jest-config]` Project config validator now emits "is not supported in an individual project configuration" instead of "probably a typing mistake" for known global-only options ([#16132](jestjs/jest#16132))
- `[jest-environment-node]` Fix `--localstorage-file` warning on Node 25+ ([#16086](jestjs/jest#16086))
- `[jest-reporters]` Apply global coverage threshold to unmatched pattern files in addition to glob/path thresholds ([#16137](jestjs/jest#16137))
- `[jest-reporters, jest-runner, jest-runtime, jest-transform]` Fix coverage report not showing correct code coverage when using `projects` config option ([#16140](jestjs/jest#16140))
- `[jest-runtime]` Resolve `expect` and `@jest/expect` from the internal module registry so test-file imports share the same `JestAssertionError` as the global `expect` ([#16130](jestjs/jest#16130))
- `[jest-runtime]` Improve CJS-from-ESM interop: `__esModule`/Babel default unwrap, broader named-export coverage, and shared CJS singleton across importers ([#16050](jestjs/jest#16050))
- `[jest-runtime]` Load `.js` files with ESM syntax but no `"type":"module"` marker as native ESM ([#16050](jestjs/jest#16050))
- `[jest-runtime]` Extend the `.js`-with-ESM-syntax fallback to `require()` on Node v24.9+ - falls back to `require(esm)` when the CJS parser rejects ESM syntax ([#16078](jestjs/jest#16078))
- `[jest-runtime]` Fix deadlocks and double-evaluation in concurrent ESM and wasm imports ([#16050](jestjs/jest#16050))
- `[jest-runtime]` Fix error when `require()` is called after the Jest environment has been torn down ([#15951](jestjs/jest#15951))
- `[jest-runtime]` Fix missing error when `import()` is called after the Jest environment has been torn down ([#16080](jestjs/jest#16080))
- `[jest-runtime]` Fix virtual `unstable_mockModule` registrations not respected in ESM ([#16081](jestjs/jest#16081))
- `[jest-runtime]` Apply `moduleNameMapper` when resolving modules with `require.resolve()` and the `paths` option ([#16135](jestjs/jest#16135))

##### Chore & Maintenance

- `[@jest/fake-timers]` Upgrade `@sinonjs/fake-timers` ([#16139](jestjs/jest#16139))
- `[jest-runtime]` Use synchronous `linkRequests` / `instantiate` for ESM linking on Node v24.9+ ([#16063](jestjs/jest#16063))
renovate Bot added a commit to andrei-picus-tink/auto-renovate that referenced this pull request May 10, 2026
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| npm        | jest    | 30.3.0 | 30.4.2 |


## [v30.4.2](https://github.com/jestjs/jest/blob/HEAD/CHANGELOG.md#3042)

##### Fixes

- `[jest-runtime]` Fix named imports from CJS modules whose `module.exports` is a function with own-property exports ([#16150](jestjs/jest#16150))


## [v30.4.1](https://github.com/jestjs/jest/blob/HEAD/CHANGELOG.md#3041)

##### Features

- `[jest-config, jest-core, jest-runner, jest-schemas, jest-types]` Allow custom runner configuration options via tuple format `['runner-path', {options}]` ([#16141](jestjs/jest#16141))

##### Fixes

- `[jest-runtime]` Align CJS-from-ESM default export with Node: `module.exports` is always the ESM default, `__esModule` unwrapping is no longer applied ([#16143](jestjs/jest#16143))


## [v30.4.0](https://github.com/jestjs/jest/blob/HEAD/CHANGELOG.md#3040)

##### Features

- `[babel-jest]` Support collecting coverage from `.mts`, `.cts` (and other) files ([#15994](jestjs/jest#15994))
- `[jest-circus, jest-cli, jest-config, jest-core, jest-jasmine2, jest-types]` Add `--collect-tests` flag to discover and list tests without executing them ([#16006](jestjs/jest#16006))
- `[jest-config, jest-runner, jest-worker]` Add `workerGracefulExitTimeout` config option to control how long workers are given to exit before being force-killed ([#15984](jestjs/jest#15984))
- `[jest-config]` Add support for `jest.config.mts` as a valid configuration file ([#16005](jestjs/jest#16005))
- `[jest-config, jest-core, jest-reporters, jest-runner]` `verbose` and `silent` can now be set per-project; the project-level value overrides the global value for that project's tests ([#16133](jestjs/jest#16133))
- `[@jest/fake-timers]` Accept `Temporal.Duration` in `jest.advanceTimersByTime()` and `jest.advanceTimersByTimeAsync()` ([#16128](jestjs/jest#16128))
- `[@jest/fake-timers]` Accept `Temporal.Instant` and `Temporal.ZonedDateTime` in `jest.setSystemTime()` and `useFakeTimers({now})` ([#16128](jestjs/jest#16128))
- `[@jest/fake-timers]` Support faking `Temporal.Now.*` ([#16131](jestjs/jest#16131))
- `[jest-mock]` Add `clearMocksOnScope(scope)` on `ModuleMocker` for clearing every mock function exposed on a scope object ([#16088](jestjs/jest#16088))
- `[jest-resolve]` Add `canResolveSync()` on `Resolver` so callers can detect when a user-configured resolver only exports an `async` hook ([#16064](jestjs/jest#16064))
- `[jest-runtime]` Use synchronous `evaluate()` for ES modules without top-level `await` on Node versions that support it (v24.9+), and prefer the synchronous transform path when a sync transformer is configured ([#16062](jestjs/jest#16062))
- `[jest-runtime]` Support `require()` of ES modules on Node v24.9+ ([#16074](jestjs/jest#16074))
- `[jest-runtime]` Validate TC39 import attributes (`with { type: 'json' }`) on ESM imports ([#16127](jestjs/jest#16127))
- `[@jest/transform]` Add `canTransformSync(filename)` on `ScriptTransformer` so callers can pick the sync vs async transform path ([#16062](jestjs/jest#16062))
- `[jest-util]` Add `isError` helper ([#16076](jestjs/jest#16076))
- `[pretty-format]` Support React 19 ([#16123](jestjs/jest#16123))

##### Fixes

- `[expect-utils]` Fix `toStrictEqual` failing on `structuredClone` results due to cross-realm constructor mismatch ([#15959](jestjs/jest#15959))
- `[@jest/expect-utils]` Prevent `toMatchObject`/subset matching from throwing when encountering exotic iterables ([#15952](jestjs/jest#15952))
- `[fake-timers]` Convert `Date` to milliseconds before passing to `@sinonjs/fake-timers` ([#16029](jestjs/jest#16029))
- `[jest]` Export `GlobalConfig` and `ProjectConfig` TypeScript types ([#16132](jestjs/jest#16132))
- `[jest-circus]` Prevent crash when `asyncError` is undefined for non-Error throws ([#16003](jestjs/jest#16003))
- `[jest-circus, jest-jasmine2]` Include `Error.cause` in JSON `failureMessages` output ([#15967](jestjs/jest#15967))
- `[jest-config]` Fix preset path resolution on Windows when the preset uses subpath `exports` ([#15961](jestjs/jest#15961))
- `[jest-config]` Allow `collectCoverage` and `coverageProvider` in project config without a validation warning ([#16132](jestjs/jest#16132))
- `[jest-config]` Project config validator now emits "is not supported in an individual project configuration" instead of "probably a typing mistake" for known global-only options ([#16132](jestjs/jest#16132))
- `[jest-environment-node]` Fix `--localstorage-file` warning on Node 25+ ([#16086](jestjs/jest#16086))
- `[jest-reporters]` Apply global coverage threshold to unmatched pattern files in addition to glob/path thresholds ([#16137](jestjs/jest#16137))
- `[jest-reporters, jest-runner, jest-runtime, jest-transform]` Fix coverage report not showing correct code coverage when using `projects` config option ([#16140](jestjs/jest#16140))
- `[jest-runtime]` Resolve `expect` and `@jest/expect` from the internal module registry so test-file imports share the same `JestAssertionError` as the global `expect` ([#16130](jestjs/jest#16130))
- `[jest-runtime]` Improve CJS-from-ESM interop: `__esModule`/Babel default unwrap, broader named-export coverage, and shared CJS singleton across importers ([#16050](jestjs/jest#16050))
- `[jest-runtime]` Load `.js` files with ESM syntax but no `"type":"module"` marker as native ESM ([#16050](jestjs/jest#16050))
- `[jest-runtime]` Extend the `.js`-with-ESM-syntax fallback to `require()` on Node v24.9+ - falls back to `require(esm)` when the CJS parser rejects ESM syntax ([#16078](jestjs/jest#16078))
- `[jest-runtime]` Fix deadlocks and double-evaluation in concurrent ESM and wasm imports ([#16050](jestjs/jest#16050))
- `[jest-runtime]` Fix error when `require()` is called after the Jest environment has been torn down ([#15951](jestjs/jest#15951))
- `[jest-runtime]` Fix missing error when `import()` is called after the Jest environment has been torn down ([#16080](jestjs/jest#16080))
- `[jest-runtime]` Fix virtual `unstable_mockModule` registrations not respected in ESM ([#16081](jestjs/jest#16081))
- `[jest-runtime]` Apply `moduleNameMapper` when resolving modules with `require.resolve()` and the `paths` option ([#16135](jestjs/jest#16135))

##### Chore & Maintenance

- `[@jest/fake-timers]` Upgrade `@sinonjs/fake-timers` ([#16139](jestjs/jest#16139))
- `[jest-runtime]` Use synchronous `linkRequests` / `instantiate` for ESM linking on Node v24.9+ ([#16063](jestjs/jest#16063))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants