Skip to content

Commit 8b70b47

Browse files
authored
feat: support importing CommonJS from ESM files (#9842)
1 parent ed4cadb commit 8b70b47

File tree

8 files changed

+138
-11
lines changed

8 files changed

+138
-11
lines changed

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
- `[expect]` Support `async function`s in `toThrow` ([#9817](https://github.com/facebook/jest/pull/9817))
66
- `[jest-console]` Add code frame to `console.error` and `console.warn` ([#9741](https://github.com/facebook/jest/pull/9741))
7-
- `[jest-runtime, jest-jasmine2, jest-circus]` Experimental, limited ECMAScript Modules support ([#9772](https://github.com/facebook/jest/pull/9772))
7+
- `[jest-runtime, jest-jasmine2, jest-circus]` Experimental, limited ECMAScript Modules support ([#9772](https://github.com/facebook/jest/pull/9772) & [#9842](https://github.com/facebook/jest/pull/9842))
88

99
### Fixes
1010

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

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

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

+41
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
*/
77

88
import {readFileSync} from 'fs';
9+
import {createRequire} from 'module';
910
import {dirname, resolve} from 'path';
1011
import {fileURLToPath} from 'url';
12+
import staticImportedStateful from '../stateful.mjs';
1113
import {double} from '../index';
1214

1315
test('should have correct import.meta', () => {
@@ -44,3 +46,42 @@ test('dynamic import should work', async () => {
4446
expect(importedDouble).toBe(double);
4547
expect(importedDouble(1)).toBe(2);
4648
});
49+
50+
test('import cjs', async () => {
51+
const {default: half} = await import('../commonjs.cjs');
52+
53+
expect(half(4)).toBe(2);
54+
});
55+
56+
test('require(cjs) and import(cjs) should share caches', async () => {
57+
const require = createRequire(import.meta.url);
58+
59+
const {default: importedStateful} = await import('../stateful.cjs');
60+
const requiredStateful = require('../stateful.cjs');
61+
62+
expect(importedStateful()).toBe(1);
63+
expect(importedStateful()).toBe(2);
64+
expect(requiredStateful()).toBe(3);
65+
expect(importedStateful()).toBe(4);
66+
expect(requiredStateful()).toBe(5);
67+
expect(requiredStateful()).toBe(6);
68+
});
69+
70+
test('import from mjs and import(mjs) should share caches', async () => {
71+
const {default: importedStateful} = await import('../stateful.mjs');
72+
73+
expect(importedStateful()).toBe(1);
74+
expect(importedStateful()).toBe(2);
75+
expect(staticImportedStateful()).toBe(3);
76+
expect(importedStateful()).toBe(4);
77+
expect(staticImportedStateful()).toBe(5);
78+
expect(staticImportedStateful()).toBe(6);
79+
});
80+
81+
test('handle unlinked dynamic imports', async () => {
82+
const {double: deepDouble} = await import('../dynamicImport');
83+
84+
expect(deepDouble).toBe(double);
85+
86+
expect(deepDouble(4)).toBe(8);
87+
});

e2e/native-esm/commonjs.cjs

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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 = function half(num) {
9+
return num / 2;
10+
};

e2e/native-esm/dynamicImport.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
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+
export * from './index';

e2e/native-esm/stateful.cjs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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+
let num = 0;
9+
10+
module.exports = function inc() {
11+
num++;
12+
return num;
13+
};

e2e/native-esm/stateful.mjs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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+
let num = 0;
9+
10+
export default function inc() {
11+
num++;
12+
return num;
13+
}

packages/jest-runtime/src/index.ts

+51-9
Original file line numberDiff line numberDiff line change
@@ -352,16 +352,38 @@ class Runtime {
352352
importModuleDynamically: (
353353
specifier: string,
354354
referencingModule: VMModule,
355-
) =>
356-
this.loadEsmModule(
357-
this._resolveModule(referencingModule.identifier, specifier),
358-
),
355+
) => {
356+
const resolved = this._resolveModule(
357+
referencingModule.identifier,
358+
specifier,
359+
);
360+
if (
361+
this._resolver.isCoreModule(resolved) ||
362+
this.unstable_shouldLoadAsEsm(resolved)
363+
) {
364+
return this.loadEsmModule(resolved);
365+
}
366+
367+
return this.loadCjsAsEsm(
368+
referencingModule.identifier,
369+
resolved,
370+
context,
371+
);
372+
},
359373
initializeImportMeta(meta: ImportMeta) {
360374
meta.url = pathToFileURL(modulePath).href;
361375
},
362376
});
363377

364378
this._esmoduleRegistry.set(cacheKey, module);
379+
380+
await module.link((specifier: string, referencingModule: VMModule) =>
381+
this.loadEsmModule(
382+
this._resolveModule(referencingModule.identifier, specifier),
383+
),
384+
);
385+
386+
await module.evaluate();
365387
}
366388

367389
const module = this._esmoduleRegistry.get(cacheKey);
@@ -382,13 +404,33 @@ class Runtime {
382404

383405
const modulePath = this._resolveModule(from, moduleName);
384406

385-
const module = await this.loadEsmModule(modulePath);
386-
await module.link((specifier: string, referencingModule: VMModule) =>
387-
this.loadEsmModule(
388-
this._resolveModule(referencingModule.identifier, specifier),
389-
),
407+
return this.loadEsmModule(modulePath);
408+
}
409+
410+
private async loadCjsAsEsm(
411+
from: Config.Path,
412+
modulePath: Config.Path,
413+
context: VMContext,
414+
) {
415+
// CJS loaded via `import` should share cache with other CJS: https://github.com/nodejs/modules/issues/503
416+
const cjs = this.requireModuleOrMock(from, modulePath);
417+
418+
const module = new SyntheticModule(
419+
['default'],
420+
function () {
421+
// @ts-ignore: TS doesn't know what `this` is
422+
this.setExport('default', cjs);
423+
},
424+
{context, identifier: modulePath},
390425
);
426+
427+
await module.link(() => {
428+
throw new Error('This should never happen');
429+
});
430+
391431
await module.evaluate();
432+
433+
return module;
392434
}
393435

394436
requireModule<T = unknown>(

0 commit comments

Comments
 (0)