diff --git a/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap b/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap index d3b49ee4f1c8..4a6a74329208 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.0.0 runs test with native ESM 1`] = ` Test Suites: 1 passed, 1 total -Tests: 2 passed, 2 total +Tests: 3 passed, 3 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 222c038fbc52..0f7e54319151 100644 --- a/e2e/native-esm/__tests__/native-esm.test.js +++ b/e2e/native-esm/__tests__/native-esm.test.js @@ -5,9 +5,13 @@ * LICENSE file in the root directory of this source tree. */ +import {readFileSync} from 'fs'; +import {dirname, resolve} from 'path'; +import {fileURLToPath} from 'url'; import {double} from '../index'; test('should have correct import.meta', () => { + expect(typeof require).toBe('undefined'); expect(typeof jest).toBe('undefined'); expect(import.meta).toEqual({ jest: expect.anything(), @@ -21,3 +25,16 @@ test('should have correct import.meta', () => { test('should double stuff', () => { expect(double(1)).toBe(2); }); + +test('should support importing node core modules', () => { + const dir = dirname(fileURLToPath(import.meta.url)); + const packageJsonPath = resolve(dir, '../package.json'); + + expect(JSON.parse(readFileSync(packageJsonPath, 'utf8'))).toEqual({ + jest: { + testEnvironment: 'node', + transform: {}, + }, + type: 'module', + }); +}); diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 62802e44e61f..c7dda4b008c7 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -12,6 +12,9 @@ import { // @ts-ignore: experimental, not added to the types SourceTextModule, // @ts-ignore: experimental, not added to the types + SyntheticModule, + Context as VMContext, + // @ts-ignore: experimental, not added to the types Module as VMModule, compileFunction, } from 'vm'; @@ -331,6 +334,12 @@ class Runtime { invariant(context); + if (this._resolver.isCoreModule(modulePath)) { + const core = await this._importCoreModule(modulePath, context); + this._esmoduleRegistry.set(cacheKey, core); + return core; + } + const transformedFile = this.transformFile(modulePath, { isInternalModule: false, supportsDynamicImport: true, @@ -1024,6 +1033,24 @@ class Runtime { return require(moduleName); } + private _importCoreModule(moduleName: string, context: VMContext) { + const required = this._requireCoreModule(moduleName); + + return new SyntheticModule( + ['default', ...Object.keys(required)], + function () { + // @ts-ignore: TS doesn't know what `this` is + this.setExport('default', required); + Object.entries(required).forEach(([key, value]) => { + // @ts-ignore: TS doesn't know what `this` is + this.setExport(key, value); + }); + }, + // should identifier be `node://${moduleName}`? + {context, identifier: moduleName}, + ); + } + private _getMockedNativeModule(): typeof nativeModule.Module { if (this._moduleImplementation) { return this._moduleImplementation; @@ -1058,13 +1085,20 @@ class Runtime { // should we implement the class ourselves? class Module extends nativeModule.Module {} + Object.entries(nativeModule.Module).forEach(([key, value]) => { + // @ts-ignore + Module[key] = value; + }); + Module.Module = Module; if ('createRequire' in nativeModule) { Module.createRequire = createRequire; } if ('createRequireFromPath' in nativeModule) { - Module.createRequireFromPath = (filename: string | URL) => { + Module.createRequireFromPath = function createRequireFromPath( + filename: string | URL, + ) { if (typeof filename !== 'string') { const error = new TypeError( `The argument 'filename' must be string. Received '${filename}'.${ @@ -1081,7 +1115,7 @@ class Runtime { }; } if ('syncBuiltinESMExports' in nativeModule) { - Module.syncBuiltinESMExports = () => {}; + Module.syncBuiltinESMExports = function syncBuiltinESMExports() {}; } this._moduleImplementation = Module;