Skip to content

Commit

Permalink
feat: resolve reexported CJS modules as named ESM exports
Browse files Browse the repository at this point in the history
  • Loading branch information
SimenB committed Dec 30, 2020
1 parent e3d5491 commit 3d93699
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 16 deletions.
9 changes: 7 additions & 2 deletions e2e/native-esm/__tests__/native-esm.test.js
Expand Up @@ -14,7 +14,7 @@ import {fileURLToPath} from 'url';
import {jest as jestObject} from '@jest/globals';
import staticImportedStatefulFromCjs from '../fromCjs.mjs';
import {double} from '../index';
import defaultFromCjs, {namedFunction} from '../namedExport.cjs';
import defaultFromCjs, {half, namedFunction} from '../namedExport.cjs';
// eslint-disable-next-line import/named
import {bag} from '../namespaceExport.js';
import staticImportedStateful from '../stateful.mjs';
Expand Down Expand Up @@ -139,10 +139,15 @@ test('varies module cache by query', () => {
});

test('supports named imports from CJS', () => {
expect(half(4)).toBe(2);
expect(namedFunction()).toBe('hello from a named CJS function!');
expect(defaultFromCjs.default()).toBe('"default" export');

expect(Object.keys(defaultFromCjs)).toEqual(['namedFunction', 'default']);
expect(Object.keys(defaultFromCjs)).toEqual([
'half',
'namedFunction',
'default',
]);
});

test('supports file urls as imports', async () => {
Expand Down
8 changes: 8 additions & 0 deletions e2e/native-esm/commonjsNamed.cjs
@@ -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.
*/

module.exports.half = require('./commonjs.cjs');
1 change: 1 addition & 0 deletions e2e/native-esm/namedExport.cjs
Expand Up @@ -5,5 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/

module.exports = require('./commonjsNamed.cjs');
module.exports.namedFunction = () => 'hello from a named CJS function!';
module.exports.default = () => '"default" export';
41 changes: 27 additions & 14 deletions packages/jest-runtime/src/index.ts
Expand Up @@ -524,21 +524,15 @@ export default class Runtime {
// CJS loaded via `import` should share cache with other CJS: https://github.com/nodejs/modules/issues/503
const cjs = this.requireModuleOrMock(from, modulePath);

const transformedCode = this._fileTransforms.get(modulePath);
const parsedExports = this.getExportsOfCjs(modulePath);

let cjsExports: ReadonlyArray<string> = [];

if (transformedCode) {
const {exports} = parseCjs(transformedCode.code);

cjsExports = exports.filter(exportName => {
// we don't wanna respect any exports _names_ default as a named export
if (exportName === 'default') {
return false;
}
return Object.hasOwnProperty.call(cjs, exportName);
});
}
const cjsExports = [...parsedExports].filter(exportName => {
// we don't wanna respect any exports _named_ default as a named export
if (exportName === 'default') {
return false;
}
return Object.hasOwnProperty.call(cjs, exportName);
});

const module = new SyntheticModule(
[...cjsExports, 'default'],
Expand All @@ -556,6 +550,25 @@ export default class Runtime {
return evaluateSyntheticModule(module);
}

private getExportsOfCjs(modulePath: Config.Path) {
const transformedCode =
this._fileTransforms.get(modulePath)?.code ?? this.readFile(modulePath);

const {exports, reexports} = parseCjs(transformedCode);

let namedExports = new Set(exports);

reexports.forEach(reexport => {
const resolved = this._resolveModule(modulePath, reexport);

const exports = this.getExportsOfCjs(resolved);

namedExports = new Set([...namedExports, ...exports]);
});

return namedExports;
}

requireModule<T = unknown>(
from: Config.Path,
moduleName?: string,
Expand Down

0 comments on commit 3d93699

Please sign in to comment.