Skip to content

Commit

Permalink
feat: support importing CommonJS from ESM files (#9842)
Browse files Browse the repository at this point in the history
  • Loading branch information
SimenB committed Apr 19, 2020
1 parent ed4cadb commit 8b70b47
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 11 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Expand Up @@ -4,7 +4,7 @@

- `[expect]` Support `async function`s in `toThrow` ([#9817](https://github.com/facebook/jest/pull/9817))
- `[jest-console]` Add code frame to `console.error` and `console.warn` ([#9741](https://github.com/facebook/jest/pull/9741))
- `[jest-runtime, jest-jasmine2, jest-circus]` Experimental, limited ECMAScript Modules support ([#9772](https://github.com/facebook/jest/pull/9772))
- `[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))

### Fixes

Expand Down
2 changes: 1 addition & 1 deletion e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap
Expand Up @@ -2,7 +2,7 @@

exports[`on node >=12.16.0 runs test with native ESM 1`] = `
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Tests: 8 passed, 8 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites.
Expand Down
41 changes: 41 additions & 0 deletions e2e/native-esm/__tests__/native-esm.test.js
Expand Up @@ -6,8 +6,10 @@
*/

import {readFileSync} from 'fs';
import {createRequire} from 'module';
import {dirname, resolve} from 'path';
import {fileURLToPath} from 'url';
import staticImportedStateful from '../stateful.mjs';
import {double} from '../index';

test('should have correct import.meta', () => {
Expand Down Expand Up @@ -44,3 +46,42 @@ test('dynamic import should work', async () => {
expect(importedDouble).toBe(double);
expect(importedDouble(1)).toBe(2);
});

test('import cjs', async () => {
const {default: half} = await import('../commonjs.cjs');

expect(half(4)).toBe(2);
});

test('require(cjs) and import(cjs) should share caches', async () => {
const require = createRequire(import.meta.url);

const {default: importedStateful} = await import('../stateful.cjs');
const requiredStateful = require('../stateful.cjs');

expect(importedStateful()).toBe(1);
expect(importedStateful()).toBe(2);
expect(requiredStateful()).toBe(3);
expect(importedStateful()).toBe(4);
expect(requiredStateful()).toBe(5);
expect(requiredStateful()).toBe(6);
});

test('import from mjs and import(mjs) should share caches', async () => {
const {default: importedStateful} = await import('../stateful.mjs');

expect(importedStateful()).toBe(1);
expect(importedStateful()).toBe(2);
expect(staticImportedStateful()).toBe(3);
expect(importedStateful()).toBe(4);
expect(staticImportedStateful()).toBe(5);
expect(staticImportedStateful()).toBe(6);
});

test('handle unlinked dynamic imports', async () => {
const {double: deepDouble} = await import('../dynamicImport');

expect(deepDouble).toBe(double);

expect(deepDouble(4)).toBe(8);
});
10 changes: 10 additions & 0 deletions e2e/native-esm/commonjs.cjs
@@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

module.exports = function half(num) {
return num / 2;
};
8 changes: 8 additions & 0 deletions e2e/native-esm/dynamicImport.js
@@ -0,0 +1,8 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

export * from './index';
13 changes: 13 additions & 0 deletions e2e/native-esm/stateful.cjs
@@ -0,0 +1,13 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

let num = 0;

module.exports = function inc() {
num++;
return num;
};
13 changes: 13 additions & 0 deletions e2e/native-esm/stateful.mjs
@@ -0,0 +1,13 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

let num = 0;

export default function inc() {
num++;
return num;
}
60 changes: 51 additions & 9 deletions packages/jest-runtime/src/index.ts
Expand Up @@ -352,16 +352,38 @@ class Runtime {
importModuleDynamically: (
specifier: string,
referencingModule: VMModule,
) =>
this.loadEsmModule(
this._resolveModule(referencingModule.identifier, specifier),
),
) => {
const resolved = this._resolveModule(
referencingModule.identifier,
specifier,
);
if (
this._resolver.isCoreModule(resolved) ||
this.unstable_shouldLoadAsEsm(resolved)
) {
return this.loadEsmModule(resolved);
}

return this.loadCjsAsEsm(
referencingModule.identifier,
resolved,
context,
);
},
initializeImportMeta(meta: ImportMeta) {
meta.url = pathToFileURL(modulePath).href;
},
});

this._esmoduleRegistry.set(cacheKey, module);

await module.link((specifier: string, referencingModule: VMModule) =>
this.loadEsmModule(
this._resolveModule(referencingModule.identifier, specifier),
),
);

await module.evaluate();
}

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

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

const module = await this.loadEsmModule(modulePath);
await module.link((specifier: string, referencingModule: VMModule) =>
this.loadEsmModule(
this._resolveModule(referencingModule.identifier, specifier),
),
return this.loadEsmModule(modulePath);
}

private async loadCjsAsEsm(
from: Config.Path,
modulePath: Config.Path,
context: VMContext,
) {
// CJS loaded via `import` should share cache with other CJS: https://github.com/nodejs/modules/issues/503
const cjs = this.requireModuleOrMock(from, modulePath);

const module = new SyntheticModule(
['default'],
function () {
// @ts-ignore: TS doesn't know what `this` is
this.setExport('default', cjs);
},
{context, identifier: modulePath},
);

await module.link(() => {
throw new Error('This should never happen');
});

await module.evaluate();

return module;
}

requireModule<T = unknown>(
Expand Down

0 comments on commit 8b70b47

Please sign in to comment.