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 c2f152d commit d45984f
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -72,6 +72,7 @@
- `[jest-resolve, jest-runtime]` [**BREAKING**] Use `Map`s instead of objects for all cached resources ([#10968](https://github.com/facebook/jest/pull/10968))
- `[jest-runner]` [**BREAKING**] Migrate to ESM ([#10900](https://github.com/facebook/jest/pull/10900))
- `[jest-runtime]` [**BREAKING**] Remove deprecated and unnused `getSourceMapInfo` from Runtime ([#9969](https://github.com/facebook/jest/pull/9969))
- `[jest-runtime]` Detect reexports from CJS as named exports in ESM ([#10988](https://github.com/facebook/jest/pull/10988))
- `[jest-util]` No longer checking `enumerable` when adding `process.domain` ([#10862](https://github.com/facebook/jest/pull/10862))
- `[jest-validate]` [**BREAKING**] Remove `recursiveBlacklist ` option in favor of previously introduced `recursiveDenylist` ([#10650](https://github.com/facebook/jest/pull/10650))

Expand Down
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 d45984f

Please sign in to comment.