diff --git a/CHANGELOG.md b/CHANGELOG.md index 80a65e3e5d4f..b7fcc012b361 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - `[jest-haste-map]` Use watchman suffix-set option for faster file indexing. ([#11784](https://github.com/facebook/jest/pull/11784)) - `[jest-cli]` Adds a new config options `snapshotFormat` which offers a way to override any of the formatting settings which come with [pretty-format](https://www.npmjs.com/package/pretty-format#usage-with-options). ([#11654](https://github.com/facebook/jest/pull/11654)) +- `[jest-resolver]` Support `node:` prefix when importing Node core modules ([#11331](https://github.com/facebook/jest/pull/11331)) ### Fixes @@ -116,6 +117,7 @@ - `[jest-haste-map]` Add `enableSymlinks` configuration option to follow symlinks for test files ([#9351](https://github.com/facebook/jest/pull/9351)) - `[jest-repl, jest-runner]` [**BREAKING**] Run transforms over environment ([#8751](https://github.com/facebook/jest/pull/8751)) - `[jest-repl]` Add support for `testEnvironment` written in ESM ([#11232](https://github.com/facebook/jest/pull/11232)) +- `[jest-reporters]` Add static filepath property to all reporters ([#11015](https://github.com/facebook/jest/pull/11015)) - `[jest-runner]` [**BREAKING**] set exit code to 1 if test logs after teardown ([#10728](https://github.com/facebook/jest/pull/10728)) - `[jest-runner]` [**BREAKING**] Run transforms over `runner` ([#8823](https://github.com/facebook/jest/pull/8823)) - `[jest-runner]` [**BREAKING**] Run transforms over `testRunner` ([#8823](https://github.com/facebook/jest/pull/8823)) @@ -123,7 +125,6 @@ - `[jest-runner]` Add support for `testRunner` written in ESM ([#11232](https://github.com/facebook/jest/pull/11232)) - `[jest-runtime]` Detect reexports from CJS as named exports in ESM ([#10988](https://github.com/facebook/jest/pull/10988)) - `[jest-runtime]` Support for async code transformations ([#11191](https://github.com/facebook/jest/pull/11191) & [#11220](https://github.com/facebook/jest/pull/11220)) -- `[jest-reporters]` Add static filepath property to all reporters ([#11015](https://github.com/facebook/jest/pull/11015)) - `[jest-snapshot]` [**BREAKING**] Make prettier optional for inline snapshots - fall back to string replacement ([#7792](https://github.com/facebook/jest/pull/7792) & [#11192](https://github.com/facebook/jest/pull/11192)) - `[jest-snapshot]` [**BREAKING**] Run transforms over `snapshotResolver` ([#8751](https://github.com/facebook/jest/pull/8829)) - `[jest-transform]` Pass config options defined in Jest's config to transformer's `process` and `getCacheKey` functions ([#10926](https://github.com/facebook/jest/pull/10926)) diff --git a/e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap b/e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap index 7b7539f60631..e6c44c61c5e4 100644 --- a/e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap +++ b/e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap @@ -41,7 +41,7 @@ FAIL __tests__/index.js 12 | module.exports = () => 'test'; 13 | - at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:558:17) + at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:561:17) at Object.require (index.js:10:1) `; @@ -70,6 +70,6 @@ FAIL __tests__/index.js 12 | module.exports = () => 'test'; 13 | - at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:558:17) + at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:561:17) at Object.require (index.js:10:1) `; diff --git a/e2e/__tests__/__snapshots__/resolveNoFileExtensions.test.ts.snap b/e2e/__tests__/__snapshots__/resolveNoFileExtensions.test.ts.snap index d5e865161f13..284c4c43706a 100644 --- a/e2e/__tests__/__snapshots__/resolveNoFileExtensions.test.ts.snap +++ b/e2e/__tests__/__snapshots__/resolveNoFileExtensions.test.ts.snap @@ -37,6 +37,6 @@ FAIL __tests__/test.js | ^ 9 | - at Resolver.resolveModule (../../packages/jest-resolve/build/resolver.js:311:11) + at Resolver.resolveModule (../../packages/jest-resolve/build/resolver.js:313:11) at Object.require (index.js:8:18) `; diff --git a/packages/jest-resolve/src/__tests__/resolve.test.ts b/packages/jest-resolve/src/__tests__/resolve.test.ts index 2f2f56501429..56d1640b3e7c 100644 --- a/packages/jest-resolve/src/__tests__/resolve.test.ts +++ b/packages/jest-resolve/src/__tests__/resolve.test.ts @@ -74,6 +74,20 @@ describe('isCoreModule', () => { const isCore = resolver.isCoreModule('constants'); expect(isCore).toEqual(false); }); + + it('returns true if using `node:` URLs and `moduleName` is a core module.', () => { + const moduleMap = ModuleMap.create('/'); + const resolver = new Resolver(moduleMap, {} as ResolverConfig); + const isCore = resolver.isCoreModule('node:assert'); + expect(isCore).toEqual(true); + }); + + it('returns false if using `node:` URLs and `moduleName` is not a core module.', () => { + const moduleMap = ModuleMap.create('/'); + const resolver = new Resolver(moduleMap, {} as ResolverConfig); + const isCore = resolver.isCoreModule('node:not-a-core-module'); + expect(isCore).toEqual(false); + }); }); describe('findNodeModule', () => { diff --git a/packages/jest-resolve/src/isBuiltinModule.ts b/packages/jest-resolve/src/isBuiltinModule.ts index 1bac7fefdc84..df64cc3d422f 100644 --- a/packages/jest-resolve/src/isBuiltinModule.ts +++ b/packages/jest-resolve/src/isBuiltinModule.ts @@ -7,19 +7,11 @@ import module = require('module'); -// "private" api -declare const process: NodeJS.Process & { - binding(type: string): Record; -}; - +// TODO: remove when we drop support for node v10 - it is included from node v12 const EXPERIMENTAL_MODULES = ['worker_threads']; const BUILTIN_MODULES = new Set( - module.builtinModules - ? module.builtinModules.concat(EXPERIMENTAL_MODULES) - : Object.keys(process.binding('natives')) - .filter((module: string) => !/^internal\//.test(module)) - .concat(EXPERIMENTAL_MODULES), + module.builtinModules.concat(EXPERIMENTAL_MODULES), ); export default function isBuiltinModule(module: string): boolean { diff --git a/packages/jest-resolve/src/resolver.ts b/packages/jest-resolve/src/resolver.ts index a492cf198978..c0800eef94cf 100644 --- a/packages/jest-resolve/src/resolver.ts +++ b/packages/jest-resolve/src/resolver.ts @@ -135,7 +135,7 @@ export default class Resolver { moduleName: string, options?: ResolveModuleConfig, ): Config.Path | null { - const paths = (options && options.paths) || this._options.modulePaths; + const paths = options?.paths || this._options.modulePaths; const moduleDirectory = this._options.moduleDirectories; const key = dirname + path.delimiter + moduleName; const defaultPlatform = this._options.defaultPlatform; @@ -253,7 +253,9 @@ export default class Resolver { isCoreModule(moduleName: string): boolean { return ( this._options.hasCoreModules && - isBuiltinModule(moduleName) && + (isBuiltinModule(moduleName) || + (moduleName.startsWith('node:') && + isBuiltinModule(moduleName.slice('node:'.length)))) && !this._isAliasModule(moduleName) ); } @@ -313,10 +315,8 @@ export default class Resolver { getModuleID( virtualMocks: Map, from: Config.Path, - _moduleName?: string, + moduleName = '', ): string { - const moduleName = _moduleName || ''; - const key = from + path.delimiter + moduleName; const cachedModuleID = this._moduleIDCache.get(key); if (cachedModuleID) { diff --git a/packages/jest-runtime/package.json b/packages/jest-runtime/package.json index c50cf427c202..c674814e1937 100644 --- a/packages/jest-runtime/package.json +++ b/packages/jest-runtime/package.json @@ -26,6 +26,7 @@ "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.4", @@ -47,7 +48,6 @@ "@types/glob": "^7.1.1", "@types/graceful-fs": "^4.1.2", "@types/node": "^14.0.27", - "execa": "^5.0.0", "jest-environment-node": "^27.0.6", "jest-snapshot-serializer-raw": "^1.1.0" }, diff --git a/packages/jest-runtime/src/__tests__/runtime_require_module.test.js b/packages/jest-runtime/src/__tests__/runtime_require_module.test.js index 937d1bc14350..8dd185c562ac 100644 --- a/packages/jest-runtime/src/__tests__/runtime_require_module.test.js +++ b/packages/jest-runtime/src/__tests__/runtime_require_module.test.js @@ -188,6 +188,19 @@ describe('Runtime requireModule', () => { }).not.toThrow(); }); + onNodeVersions('^16.0.0', () => { + it('finds node core built-in modules with node:prefix', async () => { + const runtime = await createRuntime(__filename); + + expect(runtime.requireModule(runtime.__mockRootPath, 'fs')).toBe( + runtime.requireModule(runtime.__mockRootPath, 'node:fs'), + ); + expect(runtime.requireModule(runtime.__mockRootPath, 'module')).toBe( + runtime.requireModule(runtime.__mockRootPath, 'node:module'), + ); + }); + }); + it('finds and loads JSON files without file extension', async () => { const runtime = await createRuntime(__filename); const exports = runtime.requireModule(runtime.__mockRootPath, './JSONFile'); diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 26387c639e3e..08c0fefd1326 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -20,6 +20,7 @@ import { } from 'vm'; import {parse as parseCjs} from 'cjs-module-lexer'; import {CoverageInstrumenter, V8Coverage} from 'collect-v8-coverage'; +import execa = require('execa'); import * as fs from 'graceful-fs'; import stripBOM = require('strip-bom'); import type { @@ -149,6 +150,29 @@ const supportsTopLevelAwait = } })(); +const supportsNodeColonModulePrefixInRequire = (() => { + try { + require('node:fs'); + + return true; + } catch { + return false; + } +})(); + +const supportsNodeColonModulePrefixInImport = (() => { + const {stdout} = execa.sync( + 'node', + [ + '--eval', + 'import("node:fs").then(() => console.log(true), () => console.log(false));', + ], + {reject: false}, + ); + + return stdout === 'true'; +})(); + export default class Runtime { private readonly _cacheFS: Map; private readonly _config: Config.ProjectConfig; @@ -645,7 +669,10 @@ export default class Runtime { } if (moduleName && this._resolver.isCoreModule(moduleName)) { - return this._requireCoreModule(moduleName); + return this._requireCoreModule( + moduleName, + supportsNodeColonModulePrefixInRequire, + ); } if (!modulePath) { @@ -1333,20 +1360,28 @@ export default class Runtime { } } - private _requireCoreModule(moduleName: string) { - if (moduleName === 'process') { + private _requireCoreModule(moduleName: string, supportPrefix: boolean) { + const moduleWithoutNodePrefix = + supportPrefix && moduleName.startsWith('node:') + ? moduleName.slice('node:'.length) + : moduleName; + + if (moduleWithoutNodePrefix === 'process') { return this._environment.global.process; } - if (moduleName === 'module') { + if (moduleWithoutNodePrefix === 'module') { return this._getMockedNativeModule(); } - return require(moduleName); + return require(moduleWithoutNodePrefix); } private _importCoreModule(moduleName: string, context: VMContext) { - const required = this._requireCoreModule(moduleName); + const required = this._requireCoreModule( + moduleName, + supportsNodeColonModulePrefixInImport, + ); const module = new SyntheticModule( ['default', ...Object.keys(required)],