Skip to content

Commit 43fedb5

Browse files
authored
fix: always load .mjs files via import (#15447)
1 parent 1e60073 commit 43fedb5

File tree

6 files changed

+77
-41
lines changed

6 files changed

+77
-41
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
- [**BREAKING**] `--testPathPattern` is now `--testPathPatterns`
8686
- [**BREAKING**] Specifying `testPathPatterns` when programmatically calling `watch` must be specified as `new TestPathPatterns(patterns)`, where `TestPathPatterns` can be imported from `@jest/pattern`
8787
- `[jest-reporters, jest-runner]` Unhandled errors without stack get correctly logged to console ([#14619](https://github.com/jestjs/jest/pull/14619))
88+
- `[jest-util]` Always load `mjs` files with `import` ([#15447](https://github.com/jestjs/jest/pull/15447))
8889
- `[jest-worker]` Properly handle a circular reference error when worker tries to send an assertion fails where either the expected or actual value is circular ([#15191](https://github.com/jestjs/jest/pull/15191))
8990
- `[jest-worker]` Properly handle a BigInt when worker tries to send an assertion fails where either the expected or actual value is BigInt ([#15191](https://github.com/jestjs/jest/pull/15191))
9091

e2e/esm-config/js/jest.config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
const displayName = await Promise.resolve('Config from js file');
9+
810
export default {
9-
displayName: 'Config from js file',
11+
displayName,
1012
testEnvironment: 'node',
1113
};

e2e/esm-config/mjs/jest.config.mjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
const displayName = await Promise.resolve('Config from mjs file');
9+
810
export default {
9-
displayName: 'Config from mjs file',
11+
displayName,
1012
testEnvironment: 'node',
1113
};

packages/jest-config/src/__tests__/normalize.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,23 @@ jest
2626
...realFs,
2727
statSync: () => ({isDirectory: () => true}),
2828
};
29+
})
30+
.mock('jest-util', () => {
31+
const realUtil =
32+
jest.requireActual<typeof import('jest-util')>('jest-util');
33+
34+
return {
35+
...realUtil,
36+
requireOrImportModule: (filePath: string, interop = true) => {
37+
const result = require(filePath);
38+
39+
if (interop) {
40+
return realUtil.interopRequireDefault(result).default;
41+
}
42+
43+
return result;
44+
},
45+
};
2946
});
3047

3148
let root: string;

packages/jest-util/src/requireOrImportModule.ts

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,39 @@ import {isAbsolute} from 'path';
99
import {pathToFileURL} from 'url';
1010
import interopRequireDefault from './interopRequireDefault';
1111

12+
async function importModule(
13+
filePath: string,
14+
applyInteropRequireDefault: boolean,
15+
) {
16+
try {
17+
const moduleUrl = pathToFileURL(filePath);
18+
19+
// node `import()` supports URL, but TypeScript doesn't know that
20+
const importedModule = await import(
21+
/* webpackIgnore: true */ moduleUrl.href
22+
);
23+
24+
if (!applyInteropRequireDefault) {
25+
return importedModule;
26+
}
27+
28+
if (!importedModule.default) {
29+
throw new Error(
30+
`Jest: Failed to load ESM at ${filePath} - did you use a default export?`,
31+
);
32+
}
33+
34+
return importedModule.default;
35+
} catch (error: any) {
36+
if (error.message === 'Not supported') {
37+
throw new Error(
38+
`Jest: Your version of Node does not support dynamic import - please enable it or use a .cjs file extension for file ${filePath}`,
39+
);
40+
}
41+
throw error;
42+
}
43+
}
44+
1245
export default async function requireOrImportModule<T>(
1346
filePath: string,
1447
applyInteropRequireDefault = true,
@@ -19,40 +52,21 @@ export default async function requireOrImportModule<T>(
1952
);
2053
}
2154
try {
55+
if (filePath.endsWith('.mjs')) {
56+
return importModule(filePath, applyInteropRequireDefault);
57+
}
58+
2259
const requiredModule = require(filePath);
2360
if (!applyInteropRequireDefault) {
2461
return requiredModule;
2562
}
2663
return interopRequireDefault(requiredModule).default;
2764
} catch (error: any) {
28-
if (error.code === 'ERR_REQUIRE_ESM') {
29-
try {
30-
const moduleUrl = pathToFileURL(filePath);
31-
32-
// node `import()` supports URL, but TypeScript doesn't know that
33-
const importedModule = await import(
34-
/* webpackIgnore: true */ moduleUrl.href
35-
);
36-
37-
if (!applyInteropRequireDefault) {
38-
return importedModule;
39-
}
40-
41-
if (!importedModule.default) {
42-
throw new Error(
43-
`Jest: Failed to load ESM at ${filePath} - did you use a default export?`,
44-
);
45-
}
46-
47-
return importedModule.default;
48-
} catch (innerError: any) {
49-
if (innerError.message === 'Not supported') {
50-
throw new Error(
51-
`Jest: Your version of Node does not support dynamic import - please enable it or use a .cjs file extension for file ${filePath}`,
52-
);
53-
}
54-
throw innerError;
55-
}
65+
if (
66+
error.code === 'ERR_REQUIRE_ESM' ||
67+
error.code === 'ERR_REQUIRE_ASYNC_MODULE'
68+
) {
69+
return importModule(filePath, applyInteropRequireDefault);
5670
} else {
5771
throw error;
5872
}

yarn.lock

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10555,8 +10555,8 @@ __metadata:
1055510555
linkType: hard
1055610556

1055710557
"express@npm:^4.17.3":
10558-
version: 4.21.1
10559-
resolution: "express@npm:4.21.1"
10558+
version: 4.21.2
10559+
resolution: "express@npm:4.21.2"
1056010560
dependencies:
1056110561
accepts: ~1.3.8
1056210562
array-flatten: 1.1.1
@@ -10577,7 +10577,7 @@ __metadata:
1057710577
methods: ~1.1.2
1057810578
on-finished: 2.4.1
1057910579
parseurl: ~1.3.3
10580-
path-to-regexp: 0.1.10
10580+
path-to-regexp: 0.1.12
1058110581
proxy-addr: ~2.0.7
1058210582
qs: 6.13.0
1058310583
range-parser: ~1.2.1
@@ -10589,7 +10589,7 @@ __metadata:
1058910589
type-is: ~1.6.18
1059010590
utils-merge: 1.0.1
1059110591
vary: ~1.1.2
10592-
checksum: 5ac2b26d8aeddda5564fc0907227d29c100f90c0ead2ead9d474dc5108e8fb306c2de2083c4e3ba326e0906466f2b73417dbac16961f4075ff9f03785fd940fe
10592+
checksum: 3aef1d355622732e20b8f3a7c112d4391d44e2131f4f449e1f273a309752a41abfad714e881f177645517cbe29b3ccdc10b35e7e25c13506114244a5b72f549d
1059310593
languageName: node
1059410594
linkType: hard
1059510595

@@ -16032,11 +16032,11 @@ __metadata:
1603216032
linkType: hard
1603316033

1603416034
"nanoid@npm:^3.3.7":
16035-
version: 3.3.7
16036-
resolution: "nanoid@npm:3.3.7"
16035+
version: 3.3.8
16036+
resolution: "nanoid@npm:3.3.8"
1603716037
bin:
1603816038
nanoid: bin/nanoid.cjs
16039-
checksum: d36c427e530713e4ac6567d488b489a36582ef89da1d6d4e3b87eded11eb10d7042a877958c6f104929809b2ab0bafa17652b076cdf84324aa75b30b722204f2
16039+
checksum: dfe0adbc0c77e9655b550c333075f51bb28cfc7568afbf3237249904f9c86c9aaaed1f113f0fddddba75673ee31c758c30c43d4414f014a52a7a626efc5958c9
1604016040
languageName: node
1604116041
linkType: hard
1604216042

@@ -17047,10 +17047,10 @@ __metadata:
1704717047
languageName: node
1704817048
linkType: hard
1704917049

17050-
"path-to-regexp@npm:0.1.10":
17051-
version: 0.1.10
17052-
resolution: "path-to-regexp@npm:0.1.10"
17053-
checksum: ab7a3b7a0b914476d44030340b0a65d69851af2a0f33427df1476100ccb87d409c39e2182837a96b98fb38c4ef2ba6b87bdad62bb70a2c153876b8061760583c
17050+
"path-to-regexp@npm:0.1.12":
17051+
version: 0.1.12
17052+
resolution: "path-to-regexp@npm:0.1.12"
17053+
checksum: ab237858bee7b25ecd885189f175ab5b5161e7b712b360d44f5c4516b8d271da3e4bf7bf0a7b9153ecb04c7d90ce8ff5158614e1208819cf62bac2b08452722e
1705417054
languageName: node
1705517055
linkType: hard
1705617056

0 commit comments

Comments
 (0)