[Bug]: `require()` applies the `node` export condition in the jsdom environment
Disclosure: I found the issue while refactoring OpenTelemetry package.json files, Claude designed and ran the repro
Version
30.5.2 (also 30.0.0; 29.7.0 is not affected)
Steps to reproduce
Create a local package
pkg/package.json:{ "name": "pkg", "version": "1.0.0", "exports": { "node": "./node.js", "default": "./browser.js" } }with
pkg/node.jscontainingmodule.exports = 'node';andpkg/browser.jscontainingmodule.exports = 'browser';.Add it as a dependency (
"pkg": "file:./pkg") and installjest@30andjest-environment-jsdom@30.Add
test/jsdom.test.js:/** * @jest-environment jsdom */ test('require() under jsdom does not apply the node condition', () => { expect(require('pkg')).toBe('browser'); });Run
jest.
Expected behavior
The test passes. jsdom's export conditions are ['browser'], so require('pkg') should skip the node branch and resolve ./browser.js. This is what Jest 29.7.0 does.
Actual behavior
Expected: "browser"
Received: "node"Setting testEnvironmentOptions.customExportConditions: ['browser'] gives the same result, so a jsdom test cannot opt out of the node condition for require().
Additional context
In jest-runtime, the CommonJS condition list always contains node:
this.cjsConditions = [...new Set(['require', ...(supportsSyncEvaluate ? ['module-sync'] : []), 'node', 'default', ...envExportConditions])];
this.esmConditions = [...new Set(['import', 'module-sync', 'default', ...envExportConditions])];node was added in #15647, which fixed a beta regression after the move to unrs-resolver in #15619 (see #15653). Before that, the list was ['require', 'default', ...envExportConditions], as it still is in 30.0.0-beta.3 and 29.7.0. jest-environment-node already supplies node through its own customExportConditions (['node', 'node-addons']), so the hard-coded entry only changes environments that leave node out, such as jsdom. It also makes CJS and ESM resolve differently in the same environment, since esmConditions has no node.
This affects packages that follow the Node.js recommendation to branch on node and put the universal implementation under default (https://nodejs.org/api/packages.html#conditional-exports). Under jsdom, Jest 30 loads their Node.js implementation.
A possible fix is to drop node from cjsConditions and leave it to the environment's exportConditions(), unless #15647 needed it for a case the environment conditions do not cover.
Environment
System:
OS: macOS 27.0
CPU: (10) arm64 Apple M4
Binaries:
Node: 26.9.0
npm: 12.1.0
npmPackages:
jest: ^30.5.2 => 30.5.2Source: jestjs/jest