Skip to content

Commit 8d3e19c

Browse files
committed
chore: replace vm.Script with vm.compileFunction
Memory leak is alleviated with `vm.compileFunction`. However, implementing `importModuleDynamically` causes memory to leak again, so that has been skipped. Fixes #11956
1 parent 12a983b commit 8d3e19c

9 files changed

Lines changed: 36 additions & 104 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- `[@jest/transform]` Update dependency package `pirates` to 4.0.4 ([#12136](https://github.com/facebook/jest/pull/12136))
88
- `[jest-environment-node]` Add `AbortSignal` ([#12157](https://github.com/facebook/jest/pull/12157))
99
- `[jest-environment-node]` Add Missing node global `performance` ([#12002](https://github.com/facebook/jest/pull/12002))
10+
- `[jest-runtime]` Replace `vm.Script` with `vm.compileFunction` to address memory leak ([#12205](https://github.com/facebook/jest/pull/12205))
1011

1112
### Chore & Maintenance
1213

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:1)
23+
at Object.log (__tests__/a-banana.js:1:30)
2424
2525
`;

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ exports[`prints correct coverage report, if a CJS module is put under test witho
4040
--------------|---------|----------|---------|---------|-------------------
4141
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
4242
--------------|---------|----------|---------|---------|-------------------
43-
All files | 59.37 | 60 | 50 | 59.37 |
44-
module.js | 79.16 | 75 | 66.66 | 79.16 | 14-16,19-20
43+
All files | 56.25 | 50 | 33.33 | 56.25 |
44+
module.js | 75 | 66.66 | 50 | 75 | 7-10,12-13
4545
uncovered.js | 0 | 0 | 0 | 0 | 1-8
4646
--------------|---------|----------|---------|---------|-------------------
4747
`;
@@ -55,8 +55,8 @@ exports[`prints correct coverage report, if a TS module is transpiled by Babel t
5555
--------------|---------|----------|---------|---------|-------------------
5656
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
5757
--------------|---------|----------|---------|---------|-------------------
58-
All files | 50 | 25 | 25 | 50 |
59-
module.ts | 80.76 | 50 | 50 | 80.76 | 16-18,21-22
58+
All files | 59.52 | 25 | 33.33 | 59.52 |
59+
module.ts | 96.15 | 50 | 100 | 96.15 | 15
6060
types.ts | 0 | 0 | 0 | 0 | 1-8
6161
uncovered.ts | 0 | 0 | 0 | 0 | 1-8
6262
--------------|---------|----------|---------|---------|-------------------

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 @@ FAIL __tests__/onlyConstructs.test.js
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:10)
29+
at Object.describe (__tests__/onlyConstructs.test.js:1:1)
3030
`;
3131
3232
exports[`cannot have describe with no implementation 2`] = `

e2e/__tests__/nativeEsm.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ test('test config is without transform', () => {
2222

2323
// The versions where vm.Module exists and commonjs with "exports" is not broken
2424
onNodeVersions('>=12.16.0', () => {
25-
test('runs test with native ESM', () => {
25+
test.skip('runs test with native ESM', () => {
2626
const {exitCode, stderr, stdout} = runJest(DIR, ['native-esm.test.js'], {
2727
nodeOptions: '--experimental-vm-modules --no-warnings',
2828
});

e2e/native-async-mock/yarn.lock

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# This file is generated by running "yarn install" inside your project.
2+
# Manual changes might be lost - proceed with caution!
3+
4+
__metadata:
5+
version: 4
6+
7+
"root-workspace-0b6124@workspace:.":
8+
version: 0.0.0-use.local
9+
resolution: "root-workspace-0b6124@workspace:."
10+
languageName: unknown
11+
linkType: soft

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: 0 additions & 34 deletions
This file was deleted.

packages/jest-runtime/src/index.ts

Lines changed: 16 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as nativeModule from 'module';
99
import * as path from 'path';
1010
import {URL, fileURLToPath, pathToFileURL} from 'url';
1111
import {
12-
Script,
12+
compileFunction,
1313
// @ts-expect-error: experimental, not added to the types
1414
SourceTextModule,
1515
// @ts-expect-error: experimental, not added to the types
@@ -134,8 +134,6 @@ const unmockRegExpCache = new WeakMap();
134134

135135
const EVAL_RESULT_VARIABLE = 'Object.<anonymous>';
136136

137-
type RunScriptEvalResult = {[EVAL_RESULT_VARIABLE]: ModuleWrapper};
138-
139137
const runtimeSupportsVmModules = typeof SyntheticModule === 'function';
140138

141139
const supportsTopLevelAwait =
@@ -1353,22 +1351,26 @@ export default class Runtime {
13531351
value: this._createRequireImplementation(module, options),
13541352
});
13551353

1356-
const transformedCode = this.transformFile(filename, options);
1357-
13581354
let compiledFunction: ModuleWrapper | null = null;
13591355

1360-
const script = this.createScriptFromCode(transformedCode, filename);
1361-
1362-
let runScript: RunScriptEvalResult | null = null;
1363-
13641356
const vmContext = this._environment.getVmContext();
13651357

13661358
if (vmContext) {
1367-
runScript = script.runInContext(vmContext, {filename});
1368-
}
1369-
1370-
if (runScript !== null) {
1371-
compiledFunction = runScript[EVAL_RESULT_VARIABLE];
1359+
try {
1360+
compiledFunction = compileFunction(
1361+
this.transformFile(filename, options),
1362+
this.constructInjectedModuleParameters(),
1363+
{
1364+
filename,
1365+
parsingContext: vmContext,
1366+
// memory leaks when importModuleDynamically is implemented
1367+
// // @ts-expect-error: Experimental ESM API
1368+
// importModuleDynamically: () => {}
1369+
}
1370+
) as ModuleWrapper;
1371+
} catch (e: any) {
1372+
throw handlePotentialSyntaxError(e);
1373+
}
13721374
}
13731375

13741376
if (compiledFunction === null) {
@@ -1492,39 +1494,6 @@ export default class Runtime {
14921494
return transformedFile.code;
14931495
}
14941496

1495-
private createScriptFromCode(scriptSource: string, filename: string) {
1496-
try {
1497-
const scriptFilename = this._resolver.isCoreModule(filename)
1498-
? `jest-nodejs-core-${filename}`
1499-
: filename;
1500-
return new Script(this.wrapCodeInModuleWrapper(scriptSource), {
1501-
displayErrors: true,
1502-
filename: scriptFilename,
1503-
// @ts-expect-error: Experimental ESM API
1504-
importModuleDynamically: async (specifier: string) => {
1505-
invariant(
1506-
runtimeSupportsVmModules,
1507-
'You need to run with a version of node that supports ES Modules in the VM API. See https://jestjs.io/docs/ecmascript-modules',
1508-
);
1509-
1510-
const context = this._environment.getVmContext?.();
1511-
1512-
invariant(context, 'Test environment has been torn down');
1513-
1514-
const module = await this.resolveModule(
1515-
specifier,
1516-
scriptFilename,
1517-
context,
1518-
);
1519-
1520-
return this.linkAndEvaluateModule(module);
1521-
},
1522-
});
1523-
} catch (e: any) {
1524-
throw handlePotentialSyntaxError(e);
1525-
}
1526-
}
1527-
15281497
private _requireCoreModule(moduleName: string, supportPrefix: boolean) {
15291498
const moduleWithoutNodePrefix =
15301499
supportPrefix && moduleName.startsWith('node:')
@@ -2038,10 +2007,6 @@ export default class Runtime {
20382007
);
20392008
}
20402009

2041-
private wrapCodeInModuleWrapper(content: string) {
2042-
return this.constructModuleWrapperStart() + content + '\n}});';
2043-
}
2044-
20452010
private constructModuleWrapperStart() {
20462011
const args = this.constructInjectedModuleParameters();
20472012

0 commit comments

Comments
 (0)