Skip to content

Commit f48a959

Browse files
SimenBcpojer
andauthored
feat(runtime): use compileFunction over new Script (#15461)
Co-authored-by: Christoph Nakazawa <[email protected]>
1 parent 2950cbb commit f48a959

File tree

9 files changed

+80
-96
lines changed

9 files changed

+80
-96
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
- `[jest-runtime]` [**BREAKING**] Make it mandatory to pass `globalConfig` to the `Runtime` constructor ([#15044](https://github.com/jestjs/jest/pull/15044))
4747
- `[jest-runtime]` Add `unstable_unmockModule` ([#15080](https://github.com/jestjs/jest/pull/15080))
4848
- `[jest-runtime]` Add `onGenerateMock` transformer callback for auto generated callbacks ([#15433](https://github.com/jestjs/jest/pull/15433) & [#15482](https://github.com/jestjs/jest/pull/15482))
49+
- `[jest-runtime]` [**BREAKING**] Use `vm.compileFunction` over `vm.Script` ([#15461](https://github.com/jestjs/jest/pull/15461))
4950
- `[@jest/schemas]` Upgrade `@sinclair/typebox` to v0.34 ([#15450](https://github.com/jestjs/jest/pull/15450))
5051
- `[@jest/types]` `test.each()`: Accept a readonly (`as const`) table properly ([#14565](https://github.com/jestjs/jest/pull/14565))
5152
- `[@jest/types]` Improve argument type inference passed to `test` and `describe` callback functions from `each` tables ([#14920](https://github.com/jestjs/jest/pull/14920))

e2e/__tests__/__snapshots__/consoleLogOutputWhenRunInBand.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ exports[`prints console.logs when run with forceExit 3`] = `
2020
" console.log
2121
Hey
2222
23-
at Object.<anonymous> (__tests__/a-banana.js:1:41)
23+
at Object.log (__tests__/a-banana.js:1:30)
2424
"
2525
`;

e2e/__tests__/__snapshots__/coverageProviderV8.test.ts.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ exports[`prints correct coverage report, if a CJS module is put under test witho
99
--------------|---------|----------|---------|---------|-------------------
1010
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
1111
--------------|---------|----------|---------|---------|-------------------
12-
All files | 59.37 | 60 | 50 | 59.37 |
13-
module.js | 79.16 | 75 | 66.66 | 79.16 | 14-16,19-20
12+
All files | 59.37 | 50 | 33.33 | 59.37 |
13+
module.js | 79.16 | 66.66 | 50 | 79.16 | 14-16,19-20
1414
uncovered.js | 0 | 0 | 0 | 0 | 1-8
1515
--------------|---------|----------|---------|---------|-------------------"
1616
`;

e2e/__tests__/__snapshots__/globals.test.ts.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ exports[`cannot have describe with no implementation 1`] = `
2424
Missing second argument. It must be a callback function.
2525
2626
> 1 | describe('describe, no implementation');
27-
| ^
27+
| ^
2828
29-
at Object.<anonymous> (__tests__/onlyConstructs.test.js:1:40)"
29+
at Object.describe (__tests__/onlyConstructs.test.js:1:1)"
3030
`;
3131
3232
exports[`cannot have describe with no implementation 2`] = `

packages/jest-reporters/src/CoverageReporter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ export default class CoverageReporter extends BaseReporter {
467467

468468
const converter = v8toIstanbul(
469469
res.url,
470-
fileTransform?.wrapperLength ?? 0,
470+
0,
471471
fileTransform && sourcemapContent
472472
? {
473473
originalSource: fileTransform.originalCode,

packages/jest-runtime/src/__tests__/__snapshots__/runtime_wrap.js.snap

Lines changed: 0 additions & 11 deletions
This file was deleted.

packages/jest-runtime/src/__tests__/runtime_wrap.js

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,48 @@ describe('Runtime', () => {
1313
createRuntime = require('createRuntime');
1414
});
1515

16-
describe('wrapCodeInModuleWrapper', () => {
17-
it('generates the correct args for the module wrapper', async () => {
16+
describe('constructInjectedModuleParameters', () => {
17+
it('generates the correct args', async () => {
1818
const runtime = await createRuntime(__filename);
1919

20-
expect(
21-
runtime.wrapCodeInModuleWrapper('module.exports = "Hello!"'),
22-
).toMatchSnapshot();
20+
expect(runtime.constructInjectedModuleParameters()).toEqual([
21+
'module',
22+
'exports',
23+
'require',
24+
'__dirname',
25+
'__filename',
26+
'jest',
27+
]);
2328
});
2429

2530
it('injects "extra globals"', async () => {
2631
const runtime = await createRuntime(__filename, {
2732
sandboxInjectedGlobals: ['Math'],
2833
});
2934

30-
expect(
31-
runtime.wrapCodeInModuleWrapper('module.exports = "Hello!"'),
32-
).toMatchSnapshot();
35+
expect(runtime.constructInjectedModuleParameters()).toEqual([
36+
'module',
37+
'exports',
38+
'require',
39+
'__dirname',
40+
'__filename',
41+
'jest',
42+
'Math',
43+
]);
44+
});
45+
46+
it('avoid injecting `jest` if `injectGlobals = false`', async () => {
47+
const runtime = await createRuntime(__filename, {
48+
injectGlobals: false,
49+
});
50+
51+
expect(runtime.constructInjectedModuleParameters()).toEqual([
52+
'module',
53+
'exports',
54+
'require',
55+
'__dirname',
56+
'__filename',
57+
]);
3358
});
3459
});
3560
});

packages/jest-runtime/src/index.ts

Lines changed: 39 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ import nativeModule = require('module');
99
import * as path from 'path';
1010
import {URL, fileURLToPath, pathToFileURL} from 'url';
1111
import {
12-
Script,
1312
// @ts-expect-error: experimental, not added to the types
1413
SourceTextModule,
1514
// @ts-expect-error: experimental, not added to the types
1615
SyntheticModule,
1716
type Context as VMContext,
1817
// @ts-expect-error: experimental, not added to the types
1918
type Module as VMModule,
19+
compileFunction,
2020
} from 'vm';
2121
import {parse as parseCjs} from 'cjs-module-lexer';
2222
import {CoverageInstrumenter, type V8Coverage} from 'collect-v8-coverage';
@@ -33,15 +33,12 @@ import type {
3333
import type {LegacyFakeTimers, ModernFakeTimers} from '@jest/fake-timers';
3434
import type {expect, jest} from '@jest/globals';
3535
import type {SourceMapRegistry} from '@jest/source-map';
36-
import type {
37-
RuntimeTransformResult,
38-
TestContext,
39-
V8CoverageResult,
40-
} from '@jest/test-result';
36+
import type {TestContext, V8CoverageResult} from '@jest/test-result';
4137
import {
4238
type CallerTransformOptions,
4339
type ScriptTransformer,
4440
type ShouldInstrumentOptions,
41+
type TransformResult,
4542
type TransformationOptions,
4643
handlePotentialSyntaxError,
4744
shouldInstrument,
@@ -145,10 +142,6 @@ const isWasm = (modulePath: string): boolean => modulePath.endsWith('.wasm');
145142

146143
const unmockRegExpCache = new WeakMap();
147144

148-
const EVAL_RESULT_VARIABLE = 'Object.<anonymous>';
149-
150-
type RunScriptEvalResult = {[EVAL_RESULT_VARIABLE]: ModuleWrapper};
151-
152145
const runtimeSupportsVmModules = typeof SyntheticModule === 'function';
153146

154147
const supportsNodeColonModulePrefixInRequire = (() => {
@@ -203,11 +196,11 @@ export default class Runtime {
203196
>;
204197
private readonly _sourceMapRegistry: SourceMapRegistry;
205198
private readonly _scriptTransformer: ScriptTransformer;
206-
private readonly _fileTransforms: Map<string, RuntimeTransformResult>;
199+
private readonly _fileTransforms: Map<string, TransformResult>;
207200
private readonly _fileTransformsMutex: Map<string, Promise<void>>;
208201
private _v8CoverageInstrumenter: CoverageInstrumenter | undefined;
209202
private _v8CoverageResult: V8Coverage | undefined;
210-
private _v8CoverageSources: Map<string, RuntimeTransformResult> | undefined;
203+
private _v8CoverageSources: Map<string, TransformResult> | undefined;
211204
private readonly _transitiveShouldMock: Map<string, boolean>;
212205
private _unmockList: RegExp | undefined;
213206
private readonly _virtualMocks: Map<string, boolean>;
@@ -1603,21 +1596,10 @@ export default class Runtime {
16031596

16041597
const transformedCode = this.transformFile(filename, options);
16051598

1606-
let compiledFunction: ModuleWrapper | null = null;
1607-
1608-
const script = this.createScriptFromCode(transformedCode, filename);
1609-
1610-
let runScript: RunScriptEvalResult | null = null;
1611-
1612-
const vmContext = this._environment.getVmContext();
1613-
1614-
if (vmContext) {
1615-
runScript = script.runInContext(vmContext, {filename});
1616-
}
1617-
1618-
if (runScript !== null) {
1619-
compiledFunction = runScript[EVAL_RESULT_VARIABLE];
1620-
}
1599+
const compiledFunction = this.createScriptFromCode(
1600+
transformedCode,
1601+
filename,
1602+
);
16211603

16221604
if (compiledFunction === null) {
16231605
this._logFormattedReferenceError(
@@ -1690,10 +1672,7 @@ export default class Runtime {
16901672
source,
16911673
);
16921674

1693-
this._fileTransforms.set(filename, {
1694-
...transformedFile,
1695-
wrapperLength: this.constructModuleWrapperStart().length,
1696-
});
1675+
this._fileTransforms.set(filename, transformedFile);
16971676

16981677
if (transformedFile.sourceMapPath) {
16991678
this._sourceMapRegistry.set(filename, transformedFile.sourceMapPath);
@@ -1718,10 +1697,7 @@ export default class Runtime {
17181697
);
17191698

17201699
if (this._fileTransforms.get(filename)?.code !== transformedFile.code) {
1721-
this._fileTransforms.set(filename, {
1722-
...transformedFile,
1723-
wrapperLength: 0,
1724-
});
1700+
this._fileTransforms.set(filename, transformedFile);
17251701
}
17261702

17271703
if (transformedFile.sourceMapPath) {
@@ -1731,34 +1707,39 @@ export default class Runtime {
17311707
}
17321708

17331709
private createScriptFromCode(scriptSource: string, filename: string) {
1710+
const vmContext = this._environment.getVmContext();
1711+
1712+
if (vmContext == null) {
1713+
return null;
1714+
}
1715+
17341716
try {
17351717
const scriptFilename = this._resolver.isCoreModule(filename)
17361718
? `jest-nodejs-core-${filename}`
17371719
: filename;
1738-
return new Script(this.wrapCodeInModuleWrapper(scriptSource), {
1739-
columnOffset: this._fileTransforms.get(filename)?.wrapperLength,
1740-
displayErrors: true,
1741-
filename: scriptFilename,
1742-
// @ts-expect-error: Experimental ESM API
1743-
importModuleDynamically: async (specifier: string) => {
1744-
invariant(
1745-
runtimeSupportsVmModules,
1746-
'You need to run with a version of node that supports ES Modules in the VM API. See https://jestjs.io/docs/ecmascript-modules',
1747-
);
1748-
1749-
const context = this._environment.getVmContext?.();
1750-
1751-
invariant(context, 'Test environment has been torn down');
1752-
1753-
const module = await this.resolveModule(
1754-
specifier,
1755-
scriptFilename,
1756-
context,
1757-
);
1758-
1759-
return this.linkAndEvaluateModule(module);
1720+
return compileFunction(
1721+
scriptSource,
1722+
this.constructInjectedModuleParameters(),
1723+
{
1724+
filename: scriptFilename,
1725+
// @ts-expect-error: Experimental ESM API
1726+
importModuleDynamically: async (specifier: string) => {
1727+
invariant(
1728+
runtimeSupportsVmModules,
1729+
'You need to run with a version of node that supports ES Modules in the VM API. See https://jestjs.io/docs/ecmascript-modules',
1730+
);
1731+
1732+
const module = await this.resolveModule(
1733+
specifier,
1734+
scriptFilename,
1735+
vmContext,
1736+
);
1737+
1738+
return this.linkAndEvaluateModule(module);
1739+
},
1740+
parsingContext: vmContext,
17601741
},
1761-
});
1742+
) as ModuleWrapper;
17621743
} catch (error: any) {
17631744
throw handlePotentialSyntaxError(error);
17641745
}
@@ -2484,16 +2465,6 @@ export default class Runtime {
24842465
);
24852466
}
24862467

2487-
private wrapCodeInModuleWrapper(content: string) {
2488-
return `${this.constructModuleWrapperStart() + content}\n}});`;
2489-
}
2490-
2491-
private constructModuleWrapperStart() {
2492-
const args = this.constructInjectedModuleParameters();
2493-
2494-
return `({"${EVAL_RESULT_VARIABLE}":function(${args.join(',')}){`;
2495-
}
2496-
24972468
private constructInjectedModuleParameters(): Array<string> {
24982469
return [
24992470
'module',

packages/jest-test-result/src/types.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ import type {Circus, Config, TestResult, TransformTypes} from '@jest/types';
1212
import type {IHasteFS, IModuleMap} from 'jest-haste-map';
1313
import type Resolver from 'jest-resolve';
1414

15-
export interface RuntimeTransformResult extends TransformTypes.TransformResult {
16-
wrapperLength: number;
17-
}
15+
export type RuntimeTransformResult = TransformTypes.TransformResult;
1816

1917
export type V8CoverageResult = Array<{
2018
codeTransformResult: RuntimeTransformResult | undefined;

0 commit comments

Comments
 (0)