jest-runtime
Version:
1,186 lines (1,141 loc) • 148 kB
JavaScript
/*!
* /**
* * Copyright (c) Meta Platforms, Inc. and affiliates.
* *
* * This source code is licensed under the MIT license found in the
* * LICENSE file in the root directory of this source tree.
* * /
*/
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ "./src/helpers.ts"
(__unused_webpack_module, exports) {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports.findSiblingsWithFileExtension = exports.decodePossibleOutsideJestVmPath = exports.createOutsideJestVmPath = void 0;
exports.noop = noop;
function path() {
const data = _interopRequireWildcard(require("node:path"));
path = function () {
return data;
};
return data;
}
function _glob() {
const data = require("glob");
_glob = function () {
return data;
};
return data;
}
function _slash() {
const data = _interopRequireDefault(require("slash"));
_slash = function () {
return data;
};
return data;
}
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const OUTSIDE_JEST_VM_PROTOCOL = 'jest-main:';
// String manipulation is easier here, fileURLToPath is only in newer Nodes,
// plus setting non-standard protocols on URL objects is difficult.
const createOutsideJestVmPath = path => `${OUTSIDE_JEST_VM_PROTOCOL}//${encodeURIComponent(path)}`;
exports.createOutsideJestVmPath = createOutsideJestVmPath;
const decodePossibleOutsideJestVmPath = outsideJestVmPath => {
if (outsideJestVmPath.startsWith(OUTSIDE_JEST_VM_PROTOCOL)) {
return decodeURIComponent(outsideJestVmPath.replace(new RegExp(`^${OUTSIDE_JEST_VM_PROTOCOL}//`), ''));
}
return undefined;
};
exports.decodePossibleOutsideJestVmPath = decodePossibleOutsideJestVmPath;
const findSiblingsWithFileExtension = (moduleFileExtensions, from, moduleName) => {
if (!path().isAbsolute(moduleName) && path().extname(moduleName) === '') {
const dirname = path().dirname(from);
const pathToModule = path().resolve(dirname, moduleName);
try {
const slashedDirname = (0, _slash().default)(dirname);
const matches = _glob().glob.sync(`${pathToModule}.*`, {
windowsPathsNoEscape: true
}).map(match => {
const slashedMap = (0, _slash().default)(match);
const relativePath = path().posix.relative(slashedDirname, slashedMap);
const slashedPath = path().posix.dirname(slashedMap) === slashedDirname ? `./${relativePath}` : relativePath;
return `\t'${slashedPath}'`;
}).join('\n');
if (matches) {
const foundMessage = `\n\nHowever, Jest was able to find:\n${matches}`;
const mappedModuleFileExtensions = moduleFileExtensions.map(ext => `'${ext}'`).join(', ');
return `${foundMessage}\n\nYou might want to include a file extension in your import, or update your 'moduleFileExtensions', which is currently ` + `[${mappedModuleFileExtensions}].\n\nSee https://jestjs.io/docs/configuration#modulefileextensions-arraystring`;
}
} catch {}
}
return '';
};
exports.findSiblingsWithFileExtension = findSiblingsWithFileExtension;
function noop() {
// empty
}
/***/ },
/***/ "./src/internals/CjsExportsCache.ts"
(__unused_webpack_module, exports) {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports.CjsExportsCache = void 0;
function path() {
const data = _interopRequireWildcard(require("node:path"));
path = function () {
return data;
};
return data;
}
function _cjsModuleLexer() {
const data = require("cjs-module-lexer");
_cjsModuleLexer = function () {
return data;
};
return data;
}
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// Computes (and caches) the named exports of a CJS module by static analysis
// with cjs-module-lexer, recursively walking `module.exports = require(...)`
// re-exports. Native (`.node`) addons and core-module re-exports can't be
// statically analysed, so they are loaded via the injected callbacks and the
// real export keys are read off the resulting object.
class CjsExportsCache {
cache = new Map();
resolution;
fileCache;
transformCache;
loadNativeAddon;
loadCoreReexport;
constructor(options) {
this.resolution = options.resolution;
this.fileCache = options.fileCache;
this.transformCache = options.transformCache;
this.loadNativeAddon = options.loadNativeAddon;
this.loadCoreReexport = options.loadCoreReexport;
}
// `from` is the module asking for the exports - propagated to the load
// callbacks so user mocks (`jest.mock('./addon.node', factory)`) dispatch
// against the real importer rather than an empty placeholder. The cache is
// keyed by `modulePath` only (export keys don't depend on the importer);
// `from` matters only for the cache-miss load.
getExportsOf(from, modulePath) {
const cached = this.cache.get(modulePath);
if (cached) return cached;
if (path().extname(modulePath) === '.node') {
const nativeModule = this.loadNativeAddon(from, modulePath);
const namedExports = new Set(Object.keys(nativeModule));
this.cache.set(modulePath, namedExports);
return namedExports;
}
const transformedCode = this.transformCache.getCachedSource(modulePath) ?? this.fileCache.readFile(modulePath);
const {
exports,
reexports
} = (0, _cjsModuleLexer().parse)(transformedCode);
const namedExports = new Set(exports);
for (const reexport of reexports) {
if (this.resolution.isCoreModule(reexport)) {
const coreExports = this.loadCoreReexport(modulePath, reexport);
if (coreExports !== null && typeof coreExports === 'object') {
for (const key of Object.keys(coreExports)) namedExports.add(key);
}
} else {
const resolved = this.resolution.resolveCjs(modulePath, reexport);
for (const key of this.getExportsOf(modulePath, resolved)) namedExports.add(key);
}
}
this.cache.set(modulePath, namedExports);
return namedExports;
}
clear() {
this.cache.clear();
}
}
exports.CjsExportsCache = CjsExportsCache;
/***/ },
/***/ "./src/internals/CjsLoader.ts"
(__unused_webpack_module, exports, __webpack_require__) {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports.CjsLoader = void 0;
function path() {
const data = _interopRequireWildcard(require("node:path"));
path = function () {
return data;
};
return data;
}
var _ModuleExecutor = __webpack_require__("./src/internals/ModuleExecutor.ts");
var _nodeCapabilities = __webpack_require__("./src/internals/nodeCapabilities.ts");
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
class CjsLoader {
resolution;
registries;
mockState;
transformCache;
environment;
coreModule;
executor;
requireEsm;
testState;
logFormattedReferenceError;
constructor(options) {
this.resolution = options.resolution;
this.registries = options.registries;
this.mockState = options.mockState;
this.transformCache = options.transformCache;
this.environment = options.environment;
this.coreModule = options.coreModule;
this.executor = options.executor;
this.requireEsm = options.requireEsm;
this.testState = options.testState;
this.logFormattedReferenceError = options.logFormattedReferenceError;
}
requireModule(from, moduleName, options, isRequireActual = false) {
const isInternal = options?.isInternalModule ?? false;
const moduleID = this.mockState.getCjsModuleId(from, moduleName);
let modulePath;
// Some old tests rely on this mocking behavior. Ideally we'll change this
// to be more explicit.
const moduleResource = moduleName && this.resolution.getModule(moduleName);
const manualMock = moduleName && this.resolution.getCjsMockModule(from, moduleName);
if (!options?.isInternalModule && !isRequireActual && !moduleResource && manualMock && manualMock !== this.executor.getCurrentlyExecutingManualMock() && !this.mockState.isExplicitlyUnmocked(moduleID)) {
modulePath = manualMock;
}
if (moduleName && this.resolution.isCoreModule(moduleName)) {
return this.coreModule.require(moduleName, _nodeCapabilities.supportsNodeColonModulePrefixInRequire);
}
if (!modulePath) {
modulePath = this.resolution.resolveCjs(from, moduleName);
}
if (this.resolution.shouldLoadAsEsm(modulePath)) {
if (!_nodeCapabilities.supportsSyncEvaluate) {
const error = new Error(`Must use import to load ES Module: ${modulePath}\n` + "Jest's require(ESM) requires Node v24.9+ for " + 'synchronous vm module APIs; the current Node version does not ' + 'expose them.');
error.code = 'ERR_REQUIRE_ESM';
throw error;
}
// Fast path: skip the graph walker on cache hits.
const reg = this.registries.getActiveEsmRegistry();
const cached = reg.get(modulePath);
if (cached && !(cached instanceof Promise)) {
return cached.namespace;
}
return this.requireEsm(modulePath);
}
const moduleRegistry = isInternal ? this.registries.getInternalCjsRegistry() : this.registries.getActiveCjsRegistry();
const module = moduleRegistry.get(modulePath);
if (module) {
return module.exports;
}
// We must register the pre-allocated module object first so that any
// circular dependencies that may arise while evaluating the module can
// be satisfied.
const localModule = {
children: [],
exports: {},
filename: modulePath,
id: modulePath,
isPreloading: false,
loaded: false,
path: path().dirname(modulePath)
};
moduleRegistry.set(modulePath, localModule);
try {
this.loadModule(localModule, from, moduleName, modulePath, options, moduleRegistry);
} catch (error) {
moduleRegistry.delete(modulePath);
// Mirror of `loadCjsAsEsm`'s SyntaxError fallback for `require()`.
if (_nodeCapabilities.supportsSyncEvaluate && (0, _ModuleExecutor.isCjsParseError)(error)) {
try {
return this.requireEsm(modulePath);
} catch (esmError) {
if (esmError instanceof Error && esmError.name === 'SyntaxError') {
throw error;
}
throw esmError;
}
}
throw error;
}
return localModule.exports;
}
loadModule(localModule, from, moduleName, modulePath, options, moduleRegistry) {
if (path().extname(modulePath) === '.json') {
const transformed = this.transformCache.transformJson(modulePath, options);
localModule.exports = this.environment.global.JSON.parse(transformed);
} else if (path().extname(modulePath) === '.node') {
localModule.exports = require(modulePath);
} else {
// testState gates apply only to executing JS bodies - JSON/.node go
// through pure data parsing and don't run user code in the VM.
if (this.testState.bailIfTornDown('You are trying to `require` a file after the Jest environment has been torn down.')) {
return;
}
if (!_nodeCapabilities.runtimeSupportsVmModules) {
this.testState.throwIfBetweenTests('You are trying to `require` a file outside of the scope of the test code.');
}
const fromPath = moduleName ? from : null;
const result = this.executor.exec(localModule, options, moduleRegistry, fromPath, moduleName);
if (result === 'env-disposed') {
this.logFormattedReferenceError('You are trying to `require` a file after the Jest environment has been torn down.');
process.exitCode = 1;
return;
}
}
localModule.loaded = true;
}
}
exports.CjsLoader = CjsLoader;
/***/ },
/***/ "./src/internals/EsmLoader.ts"
(__unused_webpack_module, exports, __webpack_require__) {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports.EsmLoader = void 0;
exports.validateImportAttributes = validateImportAttributes;
function path() {
const data = _interopRequireWildcard(require("node:path"));
path = function () {
return data;
};
return data;
}
function _nodeUrl() {
const data = require("node:url");
_nodeUrl = function () {
return data;
};
return data;
}
function _nodeVm() {
const data = require("node:vm");
_nodeVm = function () {
return data;
};
return data;
}
function _jestUtil() {
const data = require("jest-util");
_jestUtil = function () {
return data;
};
return data;
}
var _helpers = __webpack_require__("./src/helpers.ts");
var _ModuleExecutor = __webpack_require__("./src/internals/ModuleExecutor.ts");
var _Resolution = __webpack_require__("./src/internals/Resolution.ts");
var _nodeCapabilities = __webpack_require__("./src/internals/nodeCapabilities.ts");
var _syntheticBuilders = __webpack_require__("./src/internals/syntheticBuilders.ts");
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// `'sync-required'` is `require(esm)` (must be loaded synchronously, throw a
// typed error on edges that would normally bail). `'sync-preferred'` is the
// fast path for `await import()` (try sync; fall back to the legacy async
// loader on any unsupported edge).
// Shape of the third arg Node passes to the `module.link` callback. TC39 final
// is `{attributes}`; legacy was `{assert}`. `@types/node@18` only types the
// legacy field, so we declare both ourselves.
// TODO(jest next major): drop `assert` once we require Node 22+.
// Source-text entries carry their dep cacheKeys (used for `linkRequests`).
// Synthetic entries (mocks, core, JSON, wasm, @jest/globals) start linked
// and never appear in the link-requests pass.
// `SourceTextModule#hasAsyncGraph()` lets us prove a graph is sync-evaluable.
// `SyntheticModule` does not expose it but is by definition sync (the user
// callback is sync), so treat its absence as "not async".
function moduleHasAsyncGraph(module) {
return typeof module.hasAsyncGraph === 'function' ? module.hasAsyncGraph() : false;
}
// Mirrors Node's `require(esm)` error code so user catches work uniformly.
function makeRequireAsyncError(modulePath, detail) {
const error = new Error(`require() cannot be used to load ES Module ${modulePath}: ${detail}`);
error.code = 'ERR_REQUIRE_ASYNC_MODULE';
return error;
}
// Decode a `data:` URI specifier into its mime type and decoded code/body.
// `application/wasm` returns a Buffer; everything else returns a UTF-8 string.
const dataURIRegex = /^data:(?<mime>text\/javascript|application\/json|application\/wasm)(?:;(?<encoding>charset=utf-8|base64))?,(?<code>.*)$/;
function parseDataUri(specifier) {
const match = specifier.match(dataURIRegex);
if (!match || !match.groups) {
throw new Error('Invalid data URI');
}
const {
mime,
encoding,
code
} = match.groups;
if (mime === 'application/wasm') {
if (!encoding) throw new Error('Missing data URI encoding');
if (encoding !== 'base64') {
throw new Error(`Invalid data URI encoding: ${encoding}`);
}
return {
code: Buffer.from(code, 'base64'),
mime
};
}
if (!encoding || encoding === 'charset=utf-8') {
return {
code: decodeURIComponent(code),
mime
};
}
if (encoding === 'base64') {
return {
code: Buffer.from(code, 'base64').toString(),
mime
};
}
throw new Error(`Invalid data URI encoding: ${encoding}`);
}
// Mirrors Node's `validateAttributes` in lib/internal/modules/esm/assert.js.
// The only deliberate divergence: missing `type: 'json'` warns instead of
// throwing — see the JSON branch below.
const warnedMissingJsonAttributePairs = new Set();
// Soft cap so a long-lived process (watch mode, --runInBand) can't grow the
// set without bound. When we hit it we drop everything; users see at most one
// extra repeated warning per pair, which is benign.
const MAX_WARNED_PAIRS = 10_000;
function isJsonModule(modulePath) {
return modulePath.endsWith('.json') || modulePath.startsWith('data:application/json');
}
// Avoid dumping the full payload of data: URIs (or other very long specifiers)
// into stderr.
function describeForWarning(modulePath) {
if (modulePath.startsWith('data:')) {
const comma = modulePath.indexOf(',');
if (comma > 0) return `${modulePath.slice(0, comma)},…`;
}
return modulePath;
}
function makeImportAttributeError(code, message) {
const error = new TypeError(message);
error.code = code;
return error;
}
function validateImportAttributes(modulePath, attributes, referencingIdentifier) {
for (const key of Object.keys(attributes)) {
if (key !== 'type') {
throw makeImportAttributeError('ERR_IMPORT_ATTRIBUTE_UNSUPPORTED', `Import attribute "${key}" with value "${attributes[key]}" is not supported (importing "${modulePath}" from ${referencingIdentifier})`);
}
}
const declaredType = attributes.type;
const isJson = isJsonModule(modulePath);
if (isJson) {
if (declaredType === undefined) {
// TODO(jest next major): match Node and throw
// ERR_IMPORT_ATTRIBUTE_MISSING here. Until then, warn so existing users
// without `with { type: 'json' }` keep working.
const dedupeKey = `${referencingIdentifier}::${modulePath}`;
if (!warnedMissingJsonAttributePairs.has(dedupeKey)) {
if (warnedMissingJsonAttributePairs.size >= MAX_WARNED_PAIRS) {
warnedMissingJsonAttributePairs.clear();
}
warnedMissingJsonAttributePairs.add(dedupeKey);
const moduleLabel = describeForWarning(modulePath);
console.warn('Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. ' + `Update the import of "${moduleLabel}" (from ${referencingIdentifier}): ` + "use `with { type: 'json' }` for static imports, or pass " + "`{ with: { type: 'json' } }` as the second argument to dynamic `import()`.");
}
return;
}
if (declaredType !== 'json') {
throw makeImportAttributeError('ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE', `Module "${modulePath}" is not of type "${declaredType}"`);
}
return;
}
// Non-JSON (implicit-type) module. Per HTML spec, the default type cannot
// be re-asserted, so any explicit `type` attribute is rejected.
if (declaredType !== undefined) {
throw makeImportAttributeError('ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE', `Module "${modulePath}" is not of type "${declaredType}"`);
}
}
const ESM_TRANSFORM_OPTIONS = {
isInternalModule: false,
supportsDynamicImport: true,
supportsExportNamespaceFrom: true,
supportsStaticESM: true,
supportsTopLevelAwait: true
};
function stripFileScheme(specifier) {
return specifier.startsWith('file://') ? (0, _nodeUrl().fileURLToPath)(specifier) : specifier;
}
class EsmLoader {
resolution;
fileCache;
transformCache;
registries;
mockState;
environment;
cjsExportsCache;
coreModule;
jestGlobals;
shouldLoadAsEsm;
requireModuleOrMock;
testState;
// Used only by the legacy async path; deletable when min-Node ≥ v24.9
// (delete the block at the bottom of this file too - eslint/tsc will
// surface anything else that becomes unused).
linkingMap = new WeakMap();
evaluatingMap = new WeakMap();
constructor(options) {
this.resolution = options.resolution;
this.fileCache = options.fileCache;
this.transformCache = options.transformCache;
this.registries = options.registries;
this.mockState = options.mockState;
this.environment = options.environment;
this.cjsExportsCache = options.cjsExportsCache;
this.coreModule = options.coreModule;
this.jestGlobals = options.jestGlobals;
this.shouldLoadAsEsm = options.shouldLoadAsEsm;
this.requireModuleOrMock = options.requireModuleOrMock;
this.testState = options.testState;
}
// A `null` here means the legacy async path is mid-flight on this same
// module (registry holds a Promise from a concurrent `await import()`);
// surface as ERR_REQUIRE_ESM with actionable context.
//
// Root-level mocks (`jest.unstable_mockModule(spec)` then `require(spec)`)
// are not consulted - driving a SyntheticModule from `unlinked` to
// `evaluated` needs the async link()/evaluate() pair. Transitive-dep mocks
// still apply via the graph walker.
requireEsmModule(modulePath) {
const module = this.tryLoadGraphSync(modulePath, '', 'sync-required');
if (!module) {
const error = new Error(`Cannot require() ES Module ${modulePath} synchronously: it is currently being loaded by a concurrent \`import()\`. Await that import before calling require(), or import this module instead of requiring it.`);
error.code = 'ERR_REQUIRE_ESM';
throw error;
}
return module.namespace;
}
// Public for unit-test access. Production callers reach the sync graph
// through `requireEsmModule` (sync require entry) or via `loadEsmModule`
// (the legacy async entry, which first-tries this).
tryLoadGraphSync(rootPath, rootQuery, mode) {
if (this.testState.bailIfTornDown('You are trying to `import` a file after the Jest environment has been torn down.')) {
return null;
}
const registry = this.registries.getActiveEsmRegistry();
const rootKey = rootPath + rootQuery;
const cached = registry.get(rootKey);
if (cached) {
if (cached instanceof Promise) return null;
// The legacy `loadEsmModule` source-text branch does `registry.set`
// while the `SourceTextModule` is still `'unlinked'` (link runs later
// in `linkAndEvaluateModule`); accessing `.namespace` on a non-evaluated
// module throws `ERR_VM_MODULE_STATUS`. Surface settled entries
// (`'evaluated'` / `'errored'`); bail otherwise.
if (cached.status === 'evaluated') return cached;
if (cached.status === 'errored') throw cached.error;
return null;
}
const context = this.getContext();
if (this.transformCache.hasMutex(rootKey)) return null;
const scratch = new Map();
const worklist = [{
cacheKey: rootKey,
modulePath: rootPath
}];
while (worklist.length > 0) {
const {
cacheKey,
modulePath
} = worklist.pop();
if (scratch.has(cacheKey)) continue;
// Registry first, mutex second. Same settled-status gate as the root -
// anything in `'unlinked'` / `'linking'` / `'linked'` / `'evaluating'`
// is the legacy path mid-flight on this dep. Plugging an unlinked
// module into the parent's `linkRequests` would fail Node's link
// cascade; plugging a `'linked'` one would skip its body. Bail.
const fromRegistry = registry.get(cacheKey);
if (fromRegistry instanceof Promise) return null;
if (fromRegistry) {
if (fromRegistry.status === 'errored') throw fromRegistry.error;
if (fromRegistry.status !== 'evaluated') return null;
scratch.set(cacheKey, {
cacheKey,
kind: 'synthetic',
module: fromRegistry
});
continue;
}
if (this.transformCache.hasMutex(cacheKey)) return null;
if (this.resolution.isCoreModule(modulePath)) {
scratch.set(cacheKey, {
cacheKey,
kind: 'synthetic',
module: (0, _syntheticBuilders.buildCoreSyntheticModule)(modulePath, context, (name, prefix) => this.coreModule.require(name, prefix))
});
continue;
}
if (modulePath.startsWith('data:')) {
const built = this.buildSyncDataUriEntry(modulePath, cacheKey, context, scratch, registry, worklist, mode);
if (built === null) return null;
scratch.set(cacheKey, built);
continue;
}
if ((0, _Resolution.isWasm)(modulePath)) {
const wasmEntry = this.buildSyncWasmEntry(this.fileCache.readFileBuffer(modulePath), modulePath, cacheKey, context, scratch, registry, worklist, mode);
if (wasmEntry === null) return null;
scratch.set(cacheKey, wasmEntry);
continue;
}
if (!this.transformCache.canTransformSync(modulePath)) {
if (mode === 'sync-required') {
throw makeRequireAsyncError(modulePath, 'a configured transformer is async-only');
}
return null;
}
if (modulePath.endsWith('.json')) {
scratch.set(cacheKey, {
cacheKey,
kind: 'synthetic',
module: (0, _syntheticBuilders.buildJsonSyntheticModule)(this.transformCache.transform(modulePath, ESM_TRANSFORM_OPTIONS), modulePath, context)
});
continue;
}
const transformedCode = this.transformCache.transform(modulePath, ESM_TRANSFORM_OPTIONS);
const module = new (_nodeVm().SourceTextModule)(transformedCode, {
context,
identifier: modulePath,
importModuleDynamically: this.dynamicImport,
initializeImportMeta: meta => {
const metaUrl = (0, _nodeUrl().pathToFileURL)(modulePath).href;
meta.url = metaUrl;
// @ts-expect-error Jest uses @types/node@18.
meta.filename = modulePath;
// @ts-expect-error Jest uses @types/node@18.
meta.dirname = path().dirname(modulePath);
meta.resolve = (specifier, parent = metaUrl) => {
const parentPath = (0, _nodeUrl().fileURLToPath)(parent);
return (0, _nodeUrl().pathToFileURL)(this.resolution.resolveEsm(parentPath, specifier)).href;
};
meta.jest = this.jestGlobals.jestObjectFor(modulePath);
}
});
if (typeof module.hasTopLevelAwait === 'function' && module.hasTopLevelAwait()) {
if (mode === 'sync-required') {
throw makeRequireAsyncError(modulePath, 'top-level await');
}
return null;
}
// If we got here without `moduleRequests`, the capability gate is lying.
(0, _jestUtil().invariant)(module.moduleRequests !== undefined, `moduleRequests unavailable on ${modulePath}`);
const deps = [];
for (const {
specifier,
attributes
} of module.moduleRequests) {
const resolved = this.resolveSpecifierForSyncGraph(modulePath, specifier, context, scratch, registry, mode);
if (resolved === null) return null;
validateImportAttributes(resolved.modulePath, attributes, modulePath);
deps.push(resolved.cacheKey);
if (resolved.enqueue) worklist.push(resolved.enqueue);
}
scratch.set(cacheKey, {
cacheKey,
deps,
kind: 'source',
module
});
}
for (const entry of scratch.values()) {
if (entry.kind !== 'source') continue;
const depModules = entry.deps.map(depKey => {
const depEntry = scratch.get(depKey);
(0, _jestUtil().invariant)(depEntry, `Sync ESM graph missing dep ${depKey} for ${entry.cacheKey}. This is a bug in Jest, please report it!`);
return depEntry.module;
});
(0, _jestUtil().invariant)(typeof entry.module.linkRequests === 'function', `linkRequests unavailable on ${entry.cacheKey}`);
entry.module.linkRequests(depModules);
}
const rootEntry = scratch.get(rootKey);
(0, _jestUtil().invariant)(rootEntry, 'Sync ESM graph missing root entry');
const rootModule = rootEntry.module;
if (rootEntry.kind === 'source') {
(0, _jestUtil().invariant)(typeof rootModule.instantiate === 'function', 'instantiate unavailable on root');
rootModule.instantiate();
if (moduleHasAsyncGraph(rootModule)) {
if (mode === 'sync-required') {
let culprit = rootModule.identifier;
for (const entry of scratch.values()) {
if (entry.kind === 'source' && typeof entry.module.hasTopLevelAwait === 'function' && entry.module.hasTopLevelAwait()) {
culprit = entry.module.identifier;
break;
}
}
throw makeRequireAsyncError(rootModule.identifier, culprit === rootModule.identifier ? 'top-level await' : `a dependency uses top-level await (${culprit})`);
}
return null;
}
}
for (const entry of scratch.values()) {
if (!registry.has(entry.cacheKey)) {
registry.set(entry.cacheKey, entry.module);
}
}
rootModule.evaluate().catch(_helpers.noop);
if (rootModule.status === 'errored') {
throw rootModule.error;
}
(0, _jestUtil().invariant)(rootModule.status === 'evaluated', `Expected synchronous evaluation to complete for ${rootModule.identifier}, but module status is "${rootModule.status}". This is a bug in Jest, please report it!`);
return rootModule;
}
getContext() {
(0, _jestUtil().invariant)(typeof this.environment.getVmContext === 'function', 'ES Modules are only supported if your test environment has the `getVmContext` function');
const context = this.environment.getVmContext();
(0, _jestUtil().invariant)(context, 'Test environment has been torn down');
return context;
}
// Commits (or reuses) a synthetic-module entry under `cacheKey` in both the
// local scratch and the long-lived registry. Returns `false` when the
// registry holds something the caller must bail on: a mid-flight Promise
// from the legacy async path, or a non-evaluated module (legacy can stash
// an `'unlinked'` SourceTextModule here while link/evaluate runs).
tryCommitSynthetic(cacheKey, registry, scratch, build) {
if (scratch.has(cacheKey)) return true;
const fromRegistry = registry.get(cacheKey);
if (fromRegistry instanceof Promise) return false;
if (fromRegistry) {
const cached = fromRegistry;
if (cached.status === 'errored') throw cached.error;
if (cached.status !== 'evaluated') return false;
}
const module = fromRegistry ?? build();
if (!fromRegistry) registry.set(cacheKey, module);
scratch.set(cacheKey, {
cacheKey,
kind: 'synthetic',
module
});
return true;
}
resolveSpecifierForSyncGraph(referencingIdentifier, specifier, context, scratch, registry, mode) {
if (specifier === '@jest/globals') {
const cacheKey = `@jest/globals/${referencingIdentifier}`;
const ok = this.tryCommitSynthetic(cacheKey, registry, scratch, () => this.jestGlobals.esmGlobalsModule(referencingIdentifier, context));
return ok ? {
cacheKey,
enqueue: null,
modulePath: cacheKey
} : null;
}
if (specifier.startsWith('data:')) {
const cacheKey = specifier;
return {
cacheKey,
enqueue: {
cacheKey,
modulePath: specifier
},
modulePath: specifier
};
}
specifier = stripFileScheme(specifier);
const [specifierPath, query = ''] = specifier.split('?');
const {
shouldMock,
moduleID
} = this.mockState.shouldMockEsmSync(referencingIdentifier, specifierPath);
if (shouldMock) {
const mocked = this.importMockSync(specifierPath, moduleID, context, scratch, mode);
if (mocked === null) return null;
return {
cacheKey: mocked.cacheKey,
enqueue: null,
modulePath: specifierPath
};
}
if (this.resolution.isCoreModule(specifierPath)) {
const cacheKey = specifierPath + query;
return {
cacheKey,
enqueue: {
cacheKey,
modulePath: specifierPath
},
modulePath: specifierPath
};
}
let resolved;
try {
resolved = this.resolution.resolveEsm(referencingIdentifier, specifierPath);
} catch (error) {
if (mode === 'sync-required') throw error;
return null;
}
const cacheKey = resolved + query;
if (!resolved.endsWith('.json') && !(0, _Resolution.isWasm)(resolved) && !this.shouldLoadAsEsm(resolved)) {
const ok = this.tryCommitSynthetic(cacheKey, registry, scratch, () => this.buildCjsAsEsmSyntheticModule(referencingIdentifier, resolved, context));
return ok ? {
cacheKey,
enqueue: null,
modulePath: resolved
} : null;
}
return {
cacheKey,
enqueue: {
cacheKey,
modulePath: resolved
},
modulePath: resolved
};
}
importMockSync(moduleName, moduleID, context, scratch, mode) {
const existing = this.registries.getModuleMock(moduleID);
if (existing instanceof Promise) return null;
if (existing) {
if (existing.status === 'errored') throw existing.error;
if (!scratch.has(moduleID)) {
scratch.set(moduleID, {
cacheKey: moduleID,
kind: 'synthetic',
module: existing
});
}
return {
cacheKey: moduleID
};
}
const factory = this.mockState.getEsmFactory(moduleID);
// `shouldMockEsmSync` said this spec is mocked but no factory was
// registered.
(0, _jestUtil().invariant)(factory !== undefined, 'Attempting to import a mock without a factory');
const result = factory();
if ((0, _jestUtil().isPromise)(result)) {
if (mode === 'sync-required') {
throw makeRequireAsyncError(moduleName, 'mock factory is async');
}
return null;
}
const synth = (0, _syntheticBuilders.syntheticFromExports)(moduleName, context, result);
this.registries.setModuleMock(moduleID, synth);
scratch.set(moduleID, {
cacheKey: moduleID,
kind: 'synthetic',
module: synth
});
return {
cacheKey: moduleID
};
}
// Construct a wasm SyntheticModule for the sync graph. Wasm imports are
// resolved (sync) and enqueued like static-import deps. The SyntheticModule's
// body closure-captures `scratch`; by evaluate-cascade time, every dep entry
// is fully evaluated so `module.namespace` is safe to read.
//
// Uses `new WebAssembly.Module(bytes)` (sync, blocks on large modules).
buildSyncWasmEntry(bytes, identifier, cacheKey, context, scratch, registry, worklist, mode) {
const wasmModule = new WebAssembly.Module(bytes);
const moduleSpecToCacheKey = new Map();
for (const {
module: depSpec
} of WebAssembly.Module.imports(wasmModule)) {
if (moduleSpecToCacheKey.has(depSpec)) continue;
const resolved = this.resolveSpecifierForSyncGraph(identifier, depSpec, context, scratch, registry, mode);
if (resolved === null) return null;
moduleSpecToCacheKey.set(depSpec, resolved.cacheKey);
if (resolved.enqueue) worklist.push(resolved.enqueue);
}
const synthetic = (0, _syntheticBuilders.buildWasmSyntheticModule)(wasmModule, identifier, context, depSpec => {
const depKey = moduleSpecToCacheKey.get(depSpec);
const depEntry = scratch.get(depKey);
return depEntry.module.namespace;
});
return {
cacheKey,
kind: 'synthetic',
module: synthetic
};
}
buildSyncDataUriEntry(specifier, cacheKey, context, scratch, registry, worklist, mode) {
const esmDynamicImport = this.dynamicImport;
const {
mime,
code
} = parseDataUri(specifier);
if (mime === 'application/wasm') {
return this.buildSyncWasmEntry(new Uint8Array(code), specifier, cacheKey, context, scratch, registry, worklist, mode);
}
if (mime === 'application/json') {
return {
cacheKey,
kind: 'synthetic',
module: (0, _syntheticBuilders.buildJsonSyntheticModule)(code, specifier, context)
};
}
const module = new (_nodeVm().SourceTextModule)(code, {
context,
identifier: specifier,
importModuleDynamically: esmDynamicImport,
initializeImportMeta(meta) {
meta.url = specifier;
if (meta.url.startsWith('file://')) {
// @ts-expect-error Jest uses @types/node@18.
meta.filename = (0, _nodeUrl().fileURLToPath)(meta.url);
// @ts-expect-error Jest uses @types/node@18.
meta.dirname = path().dirname(meta.filename);
}
}
});
if (typeof module.hasTopLevelAwait === 'function' && module.hasTopLevelAwait()) {
if (mode === 'sync-required') {
throw makeRequireAsyncError(specifier, 'top-level await');
}
return null;
}
(0, _jestUtil().invariant)(module.moduleRequests !== undefined, `moduleRequests unavailable on ${specifier}`);
const deps = [];
for (const {
specifier: depSpec,
attributes
} of module.moduleRequests) {
const resolved = this.resolveSpecifierForSyncGraph(specifier, depSpec, context, scratch, registry, mode);
if (resolved === null) return null;
validateImportAttributes(resolved.modulePath, attributes, specifier);
deps.push(resolved.cacheKey);
if (resolved.enqueue) worklist.push(resolved.enqueue);
}
return {
cacheKey,
deps,
kind: 'source',
module
};
}
// Synthetic-module wrappers that close over the primitive deps. The
// `requireModuleOrMock` callback inside `buildCjsAsEsmSyntheticModule`
// is the extension-point bridge to `Runtime.requireModuleOrMock`.
buildCjsAsEsmSyntheticModule(from, modulePath, context) {
return (0, _syntheticBuilders.buildCjsAsEsmSyntheticModule)(from, modulePath, context, this.requireModuleOrMock, this.cjsExportsCache);
}
// TODO: legacy async path - everything below is deletable when min-Node
// ≥ v24.9 (the sync core handles all entry shapes). Drop the `linkingMap`
// / `evaluatingMap` fields with it.
// Called from CJS bodies via `compileFunction`'s `importModuleDynamically`.
dynamicImportFromCjs(specifier, identifier, context, importAttributes) {
return this.resolveModule(specifier, identifier, context).then(m => {
validateImportAttributes(m.identifier, importAttributes ?? {}, identifier);
return this.linkAndEvaluateModule(m);
});
}
// Public entry for `Runtime.unstable_importModule`. Runtime keeps the
// public method as the override seam; this is the body.
async loadAndEvaluate(from, moduleName) {
(0, _jestUtil().invariant)(_nodeCapabilities.runtimeSupportsVmModules, 'You need to run with a version of node that supports ES Modules in the VM API. See https://jestjs.io/docs/ecmascript-modules');
const [specifierPath, query] = (moduleName ?? '').split('?');
const modulePath = await this.resolution.resolveEsmAsync(from, specifierPath);
const module = await this.loadEsmModule(modulePath, query);
return this.linkAndEvaluateModule(module);
}
async loadEsmModule(modulePath, query = '') {
// Two gates here. `supportsSyncEvaluate` is a Node-version check: the
// sync core relies on `SyntheticModule` starting `'linked'` and on
// `evaluate()` completing sync, both of which need v22.21+ / v24.8+.
// `canResolveSync` is a configured-resolver check: with an async-only
// user resolver `findNodeModule` silently falls back to the default
// resolver and would silently miss user mappings.
if (_nodeCapabilities.supportsSyncEvaluate && this.resolution.canResolveSync()) {
const synced = this.tryLoadGraphSync(modulePath, query, 'sync-preferred');
if (synced) return synced;
}
const cacheKey = modulePath + query;
const registry = this.registries.getActiveEsmRegistry();
if (this.transformCache.hasMutex(cacheKey)) {
await this.transformCache.awaitMutex(cacheKey);
}
if (!registry.has(cacheKey)) {
const context = this.getContext();
let transformResolve;
let transformReject;
this.transformCache.setMutex(cacheKey, new Promise((resolve, reject) => {
transformResolve = resolve;
transformReject = reject;
}));
(0, _jestUtil().invariant)(transformResolve && transformReject, 'Promise initialization should be sync - please report this bug to Jest!');
try {
if ((0, _Resolution.isWasm)(modulePath)) {
const wasm = this.importWasmModule(this.fileCache.readFileBuffer(modulePath), modulePath, context);
registry.set(cacheKey, wasm);
transformResolve();
return wasm;
}
if (this.resolution.isCoreModule(modulePath)) {
const core = (0, _syntheticBuilders.evaluateSyntheticModule)((0, _syntheticBuilders.buildCoreSyntheticModule)(modulePath, context, (name, prefix) => this.coreModule.require(name, prefix)));
registry.set(cacheKey, core);
transformResolve();
return core;
}
const transformedCode = this.transformCache.canTransformSync(modulePath) ? this.transformCache.transform(modulePath, ESM_TRANSFORM_OPTIONS) : await this.transformCache.transformAsync(modulePath, ESM_TRANSFORM_OPTIONS);
let module;
if (modulePath.endsWith('.json')) {
module = (0, _syntheticBuilders.buildJsonSyntheticModule)(transformedCode, modulePath, context);
} else {
module = new (_nodeVm().SourceTextModule)(transformedCode, {
context,
identifier: modulePath,
importModuleDynamically: this.dynamicImport,
initializeImportMeta: meta => {
const metaUrl = (0, _nodeUrl().pathToFileURL)(modulePath).href;
meta.url = metaUrl;
// @ts-expect-error Jest uses @types/node@18.
meta.filename = modulePath;
// @ts-expect-error Jest uses @types/node@18.
meta.dirname = path().dirname(modulePath);
meta.resolve = (specifier, parent = metaUrl) => {
const parentPath = (0, _nodeUrl().fileURLToPath)(parent);
return (0, _nodeUrl().pathToFileURL)(this.resolution.resolveEsm(parentPath, specifier)).href;
};
meta.jest = this.jestGlobals.jestObjectFor(modulePath);
}
});
}
(0, _jestUtil().invariant)(!registry.has(cacheKey), `Module cache already has entry ${cacheKey}. This is a bug in Jest, please report it!`);
registry.set(cacheKey, module);
transformResolve();
} catch (error) {
transformReject(error);
throw error;
} finally {
this.transformCache.clearMutex(cacheKey);
}
}
const module = registry.get(cacheKey);
(0, _jestUtil().invariant)(module, 'Module cache does not contain module. This is a bug in Jest, please open up an issue');
return module;
}
async resolveModule(specifier, referencingIdentifier, context) {
if (this.testState.bailIfTornDown('You are trying to `import` a file after the Jest environment has been torn down.')) {
// @ts-expect-error -- exiting
return;
}
const registry = this.registries.getActiveEsmRegistry();
if (specifier === '@jest/globals') {
const globalsIdentifier = `@jest/globals/${referencingIdentifier}`;
const fromCache = registry.get(globalsIdentifier);
if (fromCache) {
return fromCache;
}
const globals = (0, _syntheticBuilders.evaluateSyntheticModule)(this.jestGlobals.esmGlobalsModule(referencingIdentifier, context));
registry.set(globalsIdentifier, globals);
return globals;
}
if (specifier.startsWith('data:')) {
const dataDecision = await this.mockState.shouldMockEsmAsync(referencingIdentifier, specifier);
if (dataDecision.shouldMock) {
return this.importMock(specifier, dataDecision.moduleID, context);
}
const fromCache = registry.get(specifier);
if (fromCache) {
return fromCache;
}
const {
mime,
code
} = parseDataUri(specifier);
let module;
if (mime === 'application/wasm') {
module = await this.importWasmModule(new Uint8Array(code), specifier, context);
} else if (mime === 'application/json') {
module = (0, _syntheticBuilders.buildJsonSyntheticModule)(code, specifier, context);
} else {
module = new (_nodeVm().SourceTextModule)(code, {
context,
identifier: specifier,
importModuleDynamically: this.dynamicImport,
initializeImportMeta(meta) {
meta.url = specifier;
if (meta.url.startsWith('file://')) {
// @ts-expect-error Jest uses @types/node@18.
meta.filename = (0, _nodeUrl().fileURLToPath)(meta.url);
// @ts-expect-error Jest uses @types/node@18.
meta.dirname = path().dirname(meta.filename);
}
}
});
}
registry.set(specifier, module);
return module;
}
if (specifier.startsWith('file://')) {
specifier = (0, _nodeUrl().fileURLToPath)(specifier);
}
const [specifierPath, query] = specifier.split('?');
const decision = await this.mockState.shouldMockEsmAsync(referencingIdentifier, specifierPath);
if (decision.shouldMock) {
return this.importMock(specifierPath, decision.moduleID, context);
}
const resolved = await this.resolution.resolveEsmAsync(referencingIdentifier, specifierPath);
if (resolved.endsWith('.json') || this.resolution.isCoreModule(resolved) || this.shouldLoadAsEsm(resolved)) {
return this.loadEsmModule(resolved, query);
}
return this.loadCjsAsEsm(referencingIdentifier, resolved, context);
}
async linkAndEvaluateModule(module) {
if (this.testState.bailIfTornDown('You are trying to `import` a file after the Jest environment has been torn down.')) {
// @ts-expect-error: exiting early
return;
}
// Already-errored module from a prior failed evaluation.
if (module.status === 'errored') {
throw module.error;
}
if (module.status === 'unlinked') {
this.linkingMap.set(module, module.link(async (specifier, referencingModule, extra) => {
const resolved = await this.resolveModule(specifier, referencingModule.identifier, referencingModule.context);
const extraAttrs = extra;
validateImportAttributes(resolved.identifier, extraAttrs?.attributes ?? extraAttrs?.assert ?? {}, referencingModule.identifier);
return resolved;
}));
}
const linkPromise = this.linkingMap.get(module);
if (linkPromise != null) {
await linkPromise;
} else if (module.status === 'linking') {
// Module entered 'linking' via Node's cascade (a parent's link()
// recursed into this dep without going through our code). We have no
// promise to await, so yield via setImmediate - which lets all pending
// microtasks (including Node's internal linker chain) drain - until
// linking finishes.
const deadline = Date.now() + 5000;
while (module.status === 'linking') {
if (Date.now() > deadline) {
throw new Error(`Jest: module ${module.identifier} is stuck in 'linking' state after 5 s - ` + 'this is likely a bug in Jest (please report it).');
}
await new Promise(resolve => setImmediate(resolve));
}
}
if (module.status === 'linked') {
if (_nodeCapabilities.supportsSyncEvaluate && !moduleHasAsyncGraph(module)) {
// `evaluate()` fulfills synchronously whe