diff --git a/CHANGELOG.md b/CHANGELOG.md index 31fc65468fd1..edbf9a3442cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Features +- `[jest-runtime]` add support for dynamic `import()` from CommonJS ([#10620](https://github.com/facebook/jest/pull/10620)) + ### Fixes - `[jest-runner, jest-runtime]` fix: `require.main` undefined with `createRequire()` ([#10610](https://github.com/facebook/jest/pull/10610)) diff --git a/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap b/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap index eb5d4ee7e03c..280552b01f3f 100644 --- a/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap +++ b/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap @@ -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: 12 passed, 12 total +Tests: 13 passed, 13 total Snapshots: 0 total Time: <> Ran all test suites. diff --git a/e2e/native-esm/__tests__/native-esm.test.js b/e2e/native-esm/__tests__/native-esm.test.js index d5a574cb6534..fe0470fa4842 100644 --- a/e2e/native-esm/__tests__/native-esm.test.js +++ b/e2e/native-esm/__tests__/native-esm.test.js @@ -62,6 +62,11 @@ test('import cjs', async () => { expect(half(4)).toBe(2); }); +test('import esm from cjs', async () => { + const {default: halfPromise} = await import('../fromEsm.cjs'); + expect(await halfPromise(1)).toBe(2); +}); + test('require(cjs) and import(cjs) should share caches', async () => { const require = createRequire(import.meta.url); diff --git a/e2e/native-esm/fromEsm.cjs b/e2e/native-esm/fromEsm.cjs new file mode 100644 index 000000000000..cb5d4a92b568 --- /dev/null +++ b/e2e/native-esm/fromEsm.cjs @@ -0,0 +1,12 @@ +/** + * 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 = async num => { + const {double} = await import('./dynamicImport'); + + return double(num); +}; diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 166c32671e2b..3a7c3b157084 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -378,7 +378,15 @@ class Runtime { const module = new SourceTextModule(transformedCode, { context, identifier: modulePath, - importModuleDynamically: this.linkModules.bind(this), + importModuleDynamically: ( + specifier: string, + referencingModule: VMModule, + ) => + this.linkModules( + specifier, + referencingModule.identifier, + referencingModule.context, + ), initializeImportMeta(meta: ImportMeta) { meta.url = pathToFileURL(modulePath).href; }, @@ -390,7 +398,13 @@ class Runtime { // parallel can all await it. We then await it synchronously below, so // we shouldn't get any unhandled rejections module - .link(this.linkModules.bind(this)) + .link((specifier: string, referencingModule: VMModule) => + this.linkModules( + specifier, + referencingModule.identifier, + referencingModule.context, + ), + ) .then(() => module.evaluate()) .then(() => module), ); @@ -403,17 +417,18 @@ class Runtime { return module; } - private linkModules(specifier: string, referencingModule: VMModule) { + private linkModules( + specifier: string, + referencingIdentifier: string, + context: VMContext, + ) { if (specifier === '@jest/globals') { const fromCache = this._esmoduleRegistry.get('@jest/globals'); if (fromCache) { return fromCache; } - const globals = this.getGlobalsForEsm( - referencingModule.identifier, - referencingModule.context, - ); + const globals = this.getGlobalsForEsm(referencingIdentifier, context); this._esmoduleRegistry.set('@jest/globals', globals); return globals; @@ -421,7 +436,7 @@ class Runtime { const [path, query] = specifier.split('?'); - const resolved = this._resolveModule(referencingModule.identifier, path); + const resolved = this._resolveModule(referencingIdentifier, path); if ( this._resolver.isCoreModule(resolved) || @@ -430,11 +445,7 @@ class Runtime { return this.loadEsmModule(resolved, query); } - return this.loadCjsAsEsm( - referencingModule.identifier, - resolved, - referencingModule.context, - ); + return this.loadCjsAsEsm(referencingIdentifier, resolved, context); } async unstable_importModule( @@ -1101,11 +1112,20 @@ class Runtime { private createScriptFromCode(scriptSource: string, filename: string) { try { + const scriptFilename = this._resolver.isCoreModule(filename) + ? `jest-nodejs-core-${filename}` + : filename; return new Script(this.wrapCodeInModuleWrapper(scriptSource), { displayErrors: true, - filename: this._resolver.isCoreModule(filename) - ? `jest-nodejs-core-${filename}` - : filename, + filename: scriptFilename, + // @ts-expect-error: Experimental ESM API + importModuleDynamically: (specifier: string) => { + const context = this._environment.getVmContext?.(); + + invariant(context); + + return this.linkModules(specifier, scriptFilename, context); + }, }); } catch (e) { throw handlePotentialSyntaxError(e);