Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: always load .mjs files via import #15447

Merged
merged 6 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
- [**BREAKING**] `--testPathPattern` is now `--testPathPatterns`
- [**BREAKING**] Specifying `testPathPatterns` when programmatically calling `watch` must be specified as `new TestPathPatterns(patterns)`, where `TestPathPatterns` can be imported from `@jest/pattern`
- `[jest-reporters, jest-runner]` Unhandled errors without stack get correctly logged to console ([#14619](https://github.com/jestjs/jest/pull/14619))
- `[jest-util]` Always load `mjs` files with `import` ([#15447](https://github.com/jestjs/jest/pull/15447))
- `[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))
- `[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))

Expand Down
4 changes: 3 additions & 1 deletion e2e/esm-config/js/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/

const displayName = await Promise.resolve('Config from js file');

export default {
displayName: 'Config from js file',
displayName,
testEnvironment: 'node',
};
4 changes: 3 additions & 1 deletion e2e/esm-config/mjs/jest.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/

const displayName = await Promise.resolve('Config from mjs file');

export default {
displayName: 'Config from mjs file',
displayName,
testEnvironment: 'node',
};
17 changes: 17 additions & 0 deletions packages/jest-config/src/__tests__/normalize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ jest
...realFs,
statSync: () => ({isDirectory: () => true}),
};
})
.mock('jest-util', () => {
const realUtil =
jest.requireActual<typeof import('jest-util')>('jest-util');

return {
...realUtil,
requireOrImportModule: (filePath: string, interop = true) => {
const result = require(filePath);

if (interop) {
return realUtil.interopRequireDefault(result).default;
}

return result;
},
};
});

let root: string;
Expand Down
70 changes: 42 additions & 28 deletions packages/jest-util/src/requireOrImportModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,39 @@ import {isAbsolute} from 'path';
import {pathToFileURL} from 'url';
import interopRequireDefault from './interopRequireDefault';

async function importModule(
filePath: string,
applyInteropRequireDefault: boolean,
) {
try {
const moduleUrl = pathToFileURL(filePath);

// node `import()` supports URL, but TypeScript doesn't know that
const importedModule = await import(
/* webpackIgnore: true */ moduleUrl.href
);

if (!applyInteropRequireDefault) {
return importedModule;
}

if (!importedModule.default) {
throw new Error(
`Jest: Failed to load ESM at ${filePath} - did you use a default export?`,
);
}

return importedModule.default;
} catch (error: any) {
if (error.message === 'Not supported') {
throw new Error(
`Jest: Your version of Node does not support dynamic import - please enable it or use a .cjs file extension for file ${filePath}`,
);
}
throw error;
}
}

export default async function requireOrImportModule<T>(
filePath: string,
applyInteropRequireDefault = true,
Expand All @@ -19,40 +52,21 @@ export default async function requireOrImportModule<T>(
);
}
try {
if (filePath.endsWith('.mjs')) {
return importModule(filePath, applyInteropRequireDefault);
}

const requiredModule = require(filePath);
if (!applyInteropRequireDefault) {
return requiredModule;
}
return interopRequireDefault(requiredModule).default;
} catch (error: any) {
if (error.code === 'ERR_REQUIRE_ESM') {
try {
const moduleUrl = pathToFileURL(filePath);

// node `import()` supports URL, but TypeScript doesn't know that
const importedModule = await import(
/* webpackIgnore: true */ moduleUrl.href
);

if (!applyInteropRequireDefault) {
return importedModule;
}

if (!importedModule.default) {
throw new Error(
`Jest: Failed to load ESM at ${filePath} - did you use a default export?`,
);
}

return importedModule.default;
} catch (innerError: any) {
if (innerError.message === 'Not supported') {
throw new Error(
`Jest: Your version of Node does not support dynamic import - please enable it or use a .cjs file extension for file ${filePath}`,
);
}
throw innerError;
}
if (
error.code === 'ERR_REQUIRE_ESM' ||
error.code === 'ERR_REQUIRE_ASYNC_MODULE'
) {
return importModule(filePath, applyInteropRequireDefault);
} else {
throw error;
}
Expand Down
22 changes: 11 additions & 11 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10555,8 +10555,8 @@ __metadata:
linkType: hard

"express@npm:^4.17.3":
version: 4.21.1
resolution: "express@npm:4.21.1"
version: 4.21.2
resolution: "express@npm:4.21.2"
dependencies:
accepts: ~1.3.8
array-flatten: 1.1.1
Expand All @@ -10577,7 +10577,7 @@ __metadata:
methods: ~1.1.2
on-finished: 2.4.1
parseurl: ~1.3.3
path-to-regexp: 0.1.10
path-to-regexp: 0.1.12
proxy-addr: ~2.0.7
qs: 6.13.0
range-parser: ~1.2.1
Expand All @@ -10589,7 +10589,7 @@ __metadata:
type-is: ~1.6.18
utils-merge: 1.0.1
vary: ~1.1.2
checksum: 5ac2b26d8aeddda5564fc0907227d29c100f90c0ead2ead9d474dc5108e8fb306c2de2083c4e3ba326e0906466f2b73417dbac16961f4075ff9f03785fd940fe
checksum: 3aef1d355622732e20b8f3a7c112d4391d44e2131f4f449e1f273a309752a41abfad714e881f177645517cbe29b3ccdc10b35e7e25c13506114244a5b72f549d
languageName: node
linkType: hard

Expand Down Expand Up @@ -16032,11 +16032,11 @@ __metadata:
linkType: hard

"nanoid@npm:^3.3.7":
version: 3.3.7
resolution: "nanoid@npm:3.3.7"
version: 3.3.8
resolution: "nanoid@npm:3.3.8"
bin:
nanoid: bin/nanoid.cjs
checksum: d36c427e530713e4ac6567d488b489a36582ef89da1d6d4e3b87eded11eb10d7042a877958c6f104929809b2ab0bafa17652b076cdf84324aa75b30b722204f2
checksum: dfe0adbc0c77e9655b550c333075f51bb28cfc7568afbf3237249904f9c86c9aaaed1f113f0fddddba75673ee31c758c30c43d4414f014a52a7a626efc5958c9
languageName: node
linkType: hard

Expand Down Expand Up @@ -17047,10 +17047,10 @@ __metadata:
languageName: node
linkType: hard

"path-to-regexp@npm:0.1.10":
version: 0.1.10
resolution: "path-to-regexp@npm:0.1.10"
checksum: ab7a3b7a0b914476d44030340b0a65d69851af2a0f33427df1476100ccb87d409c39e2182837a96b98fb38c4ef2ba6b87bdad62bb70a2c153876b8061760583c
"path-to-regexp@npm:0.1.12":
version: 0.1.12
resolution: "path-to-regexp@npm:0.1.12"
checksum: ab237858bee7b25ecd885189f175ab5b5161e7b712b360d44f5c4516b8d271da3e4bf7bf0a7b9153ecb04c7d90ce8ff5158614e1208819cf62bac2b08452722e
languageName: node
linkType: hard

Expand Down
Loading