Skip to content

Commit 374fe52

Browse files
authored
feat: support named exports from CJS as named ESM imports (#10673)
1 parent 1db1b6e commit 374fe52

File tree

7 files changed

+52
-2
lines changed

7 files changed

+52
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
### Features
44

5+
- `[jest-runtime]` Support named exports from CommonJS as named ES Module imports ([#10673](https://github.com/facebook/jest/pull/10673))
56
- `[jest-validate]` Add support for `recursiveDenylist` option as an alternative to `recursiveBlacklist` ([#10236](https://github.com/facebook/jest/pull/10236))
67

78
### Fixes

e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
exports[`on node ^12.16.0 || >=13.7.0 runs test with native ESM 1`] = `
44
Test Suites: 1 passed, 1 total
5-
Tests: 14 passed, 14 total
5+
Tests: 15 passed, 15 total
66
Snapshots: 0 total
77
Time: <<REPLACED>>
88
Ran all test suites.

e2e/native-esm/__tests__/native-esm.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import staticImportedStatefulWithQuery from '../stateful.mjs?query=1';
2020
import staticImportedStatefulWithAnotherQuery from '../stateful.mjs?query=2';
2121
/* eslint-enable */
2222
import {double} from '../index';
23+
import defaultFromCjs, {namedFunction} from '../namedExport.cjs';
2324

2425
test('should have correct import.meta', () => {
2526
expect(typeof require).toBe('undefined');
@@ -137,3 +138,10 @@ test('varies module cache by query', () => {
137138
expect(staticImportedStatefulWithAnotherQuery()).toBe(2);
138139
expect(staticImportedStatefulWithAnotherQuery()).toBe(3);
139140
});
141+
142+
test('supports named imports from CJS', () => {
143+
expect(namedFunction()).toBe('hello from a named CJS function!');
144+
expect(defaultFromCjs.default()).toBe('"default" export');
145+
146+
expect(Object.keys(defaultFromCjs)).toEqual(['namedFunction', 'default']);
147+
});

e2e/native-esm/namedExport.cjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
module.exports.namedFunction = () => 'hello from a named CJS function!';
9+
module.exports.default = () => '"default" export';

packages/jest-runtime/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@jest/types": "^26.6.0",
2121
"@types/yargs": "^15.0.0",
2222
"chalk": "^4.0.0",
23+
"cjs-module-lexer": "^0.4.2",
2324
"collect-v8-coverage": "^1.0.0",
2425
"exit": "^0.1.2",
2526
"glob": "^7.1.3",

packages/jest-runtime/src/index.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {
1818
Module as VMModule,
1919
} from 'vm';
2020
import * as nativeModule from 'module';
21+
// @ts-expect-error
22+
import parseCjs = require('cjs-module-lexer');
2123
import type {Config, Global} from '@jest/types';
2224
import type {
2325
Jest,
@@ -474,9 +476,30 @@ class Runtime {
474476
// CJS loaded via `import` should share cache with other CJS: https://github.com/nodejs/modules/issues/503
475477
const cjs = this.requireModuleOrMock(from, modulePath);
476478

479+
const transformedCode = this._fileTransforms.get(modulePath);
480+
481+
let cjsExports: ReadonlyArray<string> = [];
482+
483+
if (transformedCode) {
484+
const {exports} = parseCjs(transformedCode.code);
485+
486+
// @ts-expect-error
487+
cjsExports = exports.filter(exportName => {
488+
// we don't wanna respect any exports _names_ default as a named export
489+
if (exportName === 'default') {
490+
return false;
491+
}
492+
return Object.hasOwnProperty.call(cjs, exportName);
493+
});
494+
}
495+
477496
const module = new SyntheticModule(
478-
['default'],
497+
[...cjsExports, 'default'],
479498
function () {
499+
cjsExports.forEach(exportName => {
500+
// @ts-expect-error
501+
this.setExport(exportName, cjs[exportName]);
502+
});
480503
// @ts-expect-error: TS doesn't know what `this` is
481504
this.setExport('default', cjs);
482505
},

yarn.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5770,6 +5770,13 @@ __metadata:
57705770
languageName: node
57715771
linkType: hard
57725772

5773+
"cjs-module-lexer@npm:^0.4.2":
5774+
version: 0.4.2
5775+
resolution: "cjs-module-lexer@npm:0.4.2"
5776+
checksum: 2b06d73648a4c1e468f235457205984d0a7f62daaf750fa163c4d6d0965e50998d609bf2b9693aad8b55498aad460a9c2ec758f73eb60b7f988faf0eb54f53b8
5777+
languageName: node
5778+
linkType: hard
5779+
57735780
"class-utils@npm:^0.3.5":
57745781
version: 0.3.6
57755782
resolution: "class-utils@npm:0.3.6"
@@ -11850,6 +11857,7 @@ fsevents@^1.2.7:
1185011857
"@types/node": ^14.0.27
1185111858
"@types/yargs": ^15.0.0
1185211859
chalk: ^4.0.0
11860+
cjs-module-lexer: ^0.4.2
1185311861
collect-v8-coverage: ^1.0.0
1185411862
execa: ^4.0.0
1185511863
exit: ^0.1.2

0 commit comments

Comments
 (0)