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

feat: support named exports from CJS as named ESM imports #10673

Merged
merged 2 commits into from Oct 23, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,7 @@

### Features

- `[jest-runtime]` Support named exports from CommonJS as named ES Module imports ([#10673](https://github.com/facebook/jest/pull/10673))
- `[jest-validate]` Add support for `recursiveDenylist` option as an alternative to `recursiveBlacklist` ([#10236](https://github.com/facebook/jest/pull/10236))

### 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 || >=13.7.0 runs test with native ESM 1`] = `
Test Suites: 1 passed, 1 total
Tests: 14 passed, 14 total
Tests: 15 passed, 15 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites.
Expand Down
8 changes: 8 additions & 0 deletions e2e/native-esm/__tests__/native-esm.test.js
Expand Up @@ -20,6 +20,7 @@ import staticImportedStatefulWithQuery from '../stateful.mjs?query=1';
import staticImportedStatefulWithAnotherQuery from '../stateful.mjs?query=2';
/* eslint-enable */
import {double} from '../index';
import defaultFromCjs, {namedFunction} from '../namedExport.cjs';

test('should have correct import.meta', () => {
expect(typeof require).toBe('undefined');
Expand Down Expand Up @@ -137,3 +138,10 @@ test('varies module cache by query', () => {
expect(staticImportedStatefulWithAnotherQuery()).toBe(2);
expect(staticImportedStatefulWithAnotherQuery()).toBe(3);
});

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

expect(Object.keys(defaultFromCjs)).toEqual(['namedFunction', 'default']);
});
9 changes: 9 additions & 0 deletions e2e/native-esm/namedExport.cjs
@@ -0,0 +1,9 @@
/**
* 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.namedFunction = () => 'hello from a named CJS function!';
module.exports.default = () => '"default" export';
1 change: 1 addition & 0 deletions packages/jest-runtime/package.json
Expand Up @@ -20,6 +20,7 @@
"@jest/types": "^26.6.0",
"@types/yargs": "^15.0.0",
"chalk": "^4.0.0",
"cjs-module-lexer": "^0.4.2",
"collect-v8-coverage": "^1.0.0",
"exit": "^0.1.2",
"glob": "^7.1.3",
Expand Down
25 changes: 24 additions & 1 deletion packages/jest-runtime/src/index.ts
Expand Up @@ -18,6 +18,8 @@ import {
Module as VMModule,
} from 'vm';
import * as nativeModule from 'module';
// @ts-expect-error
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import parseCjs = require('cjs-module-lexer');
import type {Config, Global} from '@jest/types';
import type {
Jest,
Expand Down Expand Up @@ -474,9 +476,30 @@ 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);

let cjsExports: ReadonlyArray<string> = [];

if (transformedCode) {
const {exports} = parseCjs(transformedCode.code);
Copy link
Member Author

@SimenB SimenB Oct 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this ignore re-exports - I'm not sure what the semantics are? Where would I get the "value" from?

EDIT: I guess we could resolve the resulting path? Again, not sure what the value would be


// @ts-expect-error
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 module = new SyntheticModule(
['default'],
[...cjsExports, 'default'],
function () {
cjsExports.forEach(exportName => {
// @ts-expect-error
this.setExport(exportName, cjs[exportName]);
});
// @ts-expect-error: TS doesn't know what `this` is
this.setExport('default', cjs);
},
Expand Down
8 changes: 8 additions & 0 deletions yarn.lock
Expand Up @@ -5770,6 +5770,13 @@ __metadata:
languageName: node
linkType: hard

"cjs-module-lexer@npm:^0.4.2":
version: 0.4.2
resolution: "cjs-module-lexer@npm:0.4.2"
checksum: 2b06d73648a4c1e468f235457205984d0a7f62daaf750fa163c4d6d0965e50998d609bf2b9693aad8b55498aad460a9c2ec758f73eb60b7f988faf0eb54f53b8
languageName: node
linkType: hard

"class-utils@npm:^0.3.5":
version: 0.3.6
resolution: "class-utils@npm:0.3.6"
Expand Down Expand Up @@ -11850,6 +11857,7 @@ fsevents@^1.2.7:
"@types/node": ^14.0.27
"@types/yargs": ^15.0.0
chalk: ^4.0.0
cjs-module-lexer: ^0.4.2
collect-v8-coverage: ^1.0.0
execa: ^4.0.0
exit: ^0.1.2
Expand Down