Skip to content

Commit d3efda6

Browse files
phryneascpojer
andauthored
Fix ESM TS config loading in a CJS project (#15694)
Co-authored-by: Christoph Nakazawa <[email protected]>
1 parent 2c2586d commit d3efda6

File tree

3 files changed

+54
-4
lines changed

3 files changed

+54
-4
lines changed

e2e/__tests__/__snapshots__/jest.config.ts.test.ts.snap

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`on node >=24 invalid JS in jest.config.ts (node with native TS support) 1`] = `
44
"Error: Jest: Failed to parse the TypeScript config file <<REPLACED>>
@@ -54,6 +54,19 @@ Time: <<REPLACED>>
5454
Ran all test suites."
5555
`;
5656
57+
exports[`works with jest.config.ts with cjs contents 1`] = `
58+
"PASS __tests__/a-giraffe.js
59+
✓ giraffe"
60+
`;
61+
62+
exports[`works with jest.config.ts with cjs contents 2`] = `
63+
"Test Suites: 1 passed, 1 total
64+
Tests: 1 passed, 1 total
65+
Snapshots: 0 total
66+
Time: <<REPLACED>>
67+
Ran all test suites."
68+
`;
69+
5770
exports[`works with tsconfig.json 1`] = `
5871
"PASS __tests__/a-giraffe.js
5972
✓ giraffe"

e2e/__tests__/jest.config.ts.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,23 @@ test('works with jest.config.ts', () => {
3333
expect(summary).toMatchSnapshot();
3434
});
3535

36+
test('falls back to a loader if we encounter a ESM TS config file in a CommonJs project', () => {
37+
writeFiles(DIR, {
38+
'__tests__/a-giraffe.js': "test('giraffe', () => expect(1).toBe(1));",
39+
'jest.config.ts':
40+
"export default {testEnvironment: 'jest-environment-node', testRegex: '.*-giraffe.js'};",
41+
'package.json': '{"type":"commonjs"}',
42+
});
43+
44+
const {stderr, exitCode} = runJest(DIR, ['-w=1', '--ci=false'], {
45+
nodeOptions: '--no-warnings',
46+
});
47+
const {rest, summary} = extractSummary(stderr);
48+
expect(exitCode).toBe(0);
49+
expect(rest).toMatchSnapshot();
50+
expect(summary).toMatchSnapshot();
51+
});
52+
3653
test('works with tsconfig.json', () => {
3754
writeFiles(DIR, {
3855
'__tests__/a-giraffe.js': "test('giraffe', () => expect(1).toBe(1));",

packages/jest-config/src/readConfigFileAndSetRootDir.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,29 @@ export default async function readConfigFileAndSetRootDir(
3939
let configObject;
4040

4141
try {
42-
// @ts-expect-error: type assertion can be removed once @types/node is updated to 23 https://nodejs.org/api/process.html#processfeaturestypescript
43-
if (isTS && !process.features.typescript) {
44-
configObject = await loadTSConfigFile(configPath);
42+
if (isTS) {
43+
// @ts-expect-error: Type assertion can be removed once @types/node is updated to 23 https://nodejs.org/api/process.html#processfeaturestypescript
44+
if (process.features.typescript) {
45+
try {
46+
// Try native node TypeScript support first.
47+
configObject = await requireOrImportModule<any>(configPath);
48+
} catch (error) {
49+
if (
50+
!(
51+
error instanceof SyntaxError &&
52+
// Likely ESM in a file interpreted as CJS, which means it needs to be
53+
// compiled. We ignore the error and try to load it with a loader.
54+
error.message.match(/Unexpected token '(export|import)'/)
55+
)
56+
) {
57+
throw error;
58+
}
59+
}
60+
}
61+
// Fall back to `ts-node` etc. if this cannot be natively parsed/executed.
62+
if (!configObject) {
63+
configObject = await loadTSConfigFile(configPath);
64+
}
4565
} else if (isJSON) {
4666
const fileContent = fs.readFileSync(configPath, 'utf8');
4767
configObject = parseJson(stripJsonComments(fileContent), configPath);

0 commit comments

Comments
 (0)