Skip to content

Commit ad53773

Browse files
authored
feat: add support for compileFunction allowing us to avoid the module wrapper (#9252)
1 parent 1d8245d commit ad53773

File tree

9 files changed

+138
-41
lines changed

9 files changed

+138
-41
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,18 @@
1717
- `[jest-diff]` Add `changeColor` and `patchColor` options ([#8911](https://github.com/facebook/jest/pull/8911))
1818
- `[jest-diff]` Add `trailingSpaceFormatter` option and replace cyan with `commonColor` ([#8927](https://github.com/facebook/jest/pull/8927))
1919
- `[jest-diff]` Add `firstOrLastEmptyLineReplacement` option and export 3 `diffLines` functions ([#8955](https://github.com/facebook/jest/pull/8955))
20+
- `[jest-environment]` Add optional `compileFunction` next to `runScript` ([#9252](https://github.com/facebook/jest/pull/9252))
2021
- `[jest-environment-jsdom]` Add `fakeTimersLolex` ([#8925](https://github.com/facebook/jest/pull/8925))
2122
- `[jest-environment-node]` Add `fakeTimersLolex` ([#8925](https://github.com/facebook/jest/pull/8925))
2223
- `[jest-environment-node]` Add `queueMicrotask` ([#9140](https://github.com/facebook/jest/pull/9140))
24+
- `[jest-environment-node]` Implement `compileFunction` ([#9140](https://github.com/facebook/jest/pull/9140))
2325
- `[@jest/fake-timers]` Add Lolex as implementation of fake timers ([#8897](https://github.com/facebook/jest/pull/8897))
2426
- `[jest-get-type]` Add `BigInt` support. ([#8382](https://github.com/facebook/jest/pull/8382))
2527
- `[jest-matcher-utils]` Add `BigInt` support to `ensureNumbers` `ensureActualIsNumber`, `ensureExpectedIsNumber` ([#8382](https://github.com/facebook/jest/pull/8382))
2628
- `[jest-reporters]` Export utils for path formatting ([#9162](https://github.com/facebook/jest/pull/9162))
2729
- `[jest-runner]` Warn if a worker had to be force exited ([#8206](https://github.com/facebook/jest/pull/8206))
2830
- `[jest-runtime]` [**BREAKING**] Do not export `ScriptTransformer` - it can be imported from `@jest/transform` instead ([#9256](https://github.com/facebook/jest/pull/9256))
31+
- `[jest-runtime]` Use `JestEnvironment.compileFunction` if available to avoid the module wrapper ([#9252](https://github.com/facebook/jest/pull/9252))
2932
- `[jest-snapshot]` Display change counts in annotation lines ([#8982](https://github.com/facebook/jest/pull/8982))
3033
- `[jest-snapshot]` [**BREAKING**] Improve report when the matcher has properties ([#9104](https://github.com/facebook/jest/pull/9104))
3134
- `[jest-snapshot]` Improve colors when snapshots are updatable ([#9132](https://github.com/facebook/jest/pull/9132))

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

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,6 @@ Ran all test suites.
1818
`;
1919
2020
exports[`cannot have describe with no implementation 1`] = `
21-
FAIL __tests__/onlyConstructs.test.js
22-
● Test suite failed to run
23-
24-
Missing second argument. It must be a callback function.
25-
26-
1 |
27-
> 2 | describe('describe, no implementation');
28-
| ^
29-
3 |
30-
31-
at Object.<anonymous> (__tests__/onlyConstructs.test.js:2:14)
32-
`;
33-
34-
exports[`cannot have describe with no implementation 2`] = `
3521
Test Suites: 1 failed, 1 total
3622
Tests: 0 total
3723
Snapshots: 0 total

e2e/__tests__/errorOnDeprecated.test.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,24 @@ testFiles.forEach(testFile => {
4343
expect(result.exitCode).toBe(1);
4444
let {rest} = extractSummary(result.stderr);
4545

46-
if (
47-
nodeMajorVersion < 12 &&
48-
testFile === 'defaultTimeoutInterval.test.js'
49-
) {
46+
if (testFile === 'defaultTimeoutInterval.test.js') {
5047
const lineEntry = '(__tests__/defaultTimeoutInterval.test.js:10:3)';
5148

52-
expect(rest).toContain(`at Object.<anonymous>.test ${lineEntry}`);
49+
if (nodeMajorVersion < 10) {
50+
expect(rest).toContain(`at Object.<anonymous>.test ${lineEntry}`);
5351

54-
rest = rest.replace(
55-
`at Object.<anonymous>.test ${lineEntry}`,
56-
`at Object.<anonymous> ${lineEntry}`,
57-
);
52+
rest = rest.replace(
53+
`at Object.<anonymous>.test ${lineEntry}`,
54+
`at Object.<anonymous> ${lineEntry}`,
55+
);
56+
} else if (nodeMajorVersion < 12) {
57+
expect(rest).toContain(`at Object.test ${lineEntry}`);
58+
59+
rest = rest.replace(
60+
`at Object.test ${lineEntry}`,
61+
`at Object.<anonymous> ${lineEntry}`,
62+
);
63+
}
5864
}
5965

6066
expect(wrap(rest)).toMatchSnapshot();

e2e/__tests__/failures.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ test('not throwing Error objects', () => {
3939
expect(wrap(cleanStderr(stderr))).toMatchSnapshot();
4040
stderr = runJest(dir, ['duringTests.test.js']).stderr;
4141

42-
if (nodeMajorVersion < 12) {
42+
if (nodeMajorVersion < 10) {
4343
const lineEntry = '(__tests__/duringTests.test.js:38:8)';
4444

4545
expect(stderr).toContain(`at Object.<anonymous>.done ${lineEntry}`);
@@ -48,6 +48,15 @@ test('not throwing Error objects', () => {
4848
`at Object.<anonymous>.done ${lineEntry}`,
4949
`at Object.<anonymous> ${lineEntry}`,
5050
);
51+
} else if (nodeMajorVersion < 12) {
52+
const lineEntry = '(__tests__/duringTests.test.js:38:8)';
53+
54+
expect(stderr).toContain(`at Object.done ${lineEntry}`);
55+
56+
stderr = stderr.replace(
57+
`at Object.done ${lineEntry}`,
58+
`at Object.<anonymous> ${lineEntry}`,
59+
);
5160
}
5261

5362
expect(wrap(cleanStderr(stderr))).toMatchSnapshot();

e2e/__tests__/globals.test.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import * as path from 'path';
99
import {tmpdir} from 'os';
10+
import {compileFunction} from 'vm';
1011
import {wrap} from 'jest-snapshot-serializer-raw';
1112
import runJest from '../runJest';
1213
import {
@@ -125,7 +126,46 @@ test('cannot have describe with no implementation', () => {
125126

126127
const rest = cleanStderr(stderr);
127128
const {summary} = extractSummary(stderr);
128-
expect(wrap(rest)).toMatchSnapshot();
129+
130+
const rightTrimmedRest = rest
131+
.split('\n')
132+
.map(l => l.trimRight())
133+
.join('\n')
134+
.trim();
135+
136+
if (typeof compileFunction === 'function') {
137+
expect(rightTrimmedRest).toEqual(
138+
`
139+
FAIL __tests__/onlyConstructs.test.js
140+
● Test suite failed to run
141+
142+
Missing second argument. It must be a callback function.
143+
144+
1 |
145+
> 2 | describe('describe, no implementation');
146+
| ^
147+
3 |
148+
149+
at Object.describe (__tests__/onlyConstructs.test.js:2:5)
150+
`.trim(),
151+
);
152+
} else {
153+
expect(rightTrimmedRest).toEqual(
154+
`
155+
FAIL __tests__/onlyConstructs.test.js
156+
● Test suite failed to run
157+
158+
Missing second argument. It must be a callback function.
159+
160+
1 |
161+
> 2 | describe('describe, no implementation');
162+
| ^
163+
3 |
164+
165+
at Object.<anonymous> (__tests__/onlyConstructs.test.js:2:14)
166+
`.trim(),
167+
);
168+
}
129169
expect(wrap(summary)).toMatchSnapshot();
130170
});
131171

packages/jest-environment-node/src/index.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import {Context, Script, createContext, runInContext} from 'vm';
8+
import {
9+
Context,
10+
Script,
11+
compileFunction,
12+
createContext,
13+
runInContext,
14+
} from 'vm';
915
import {Config, Global} from '@jest/types';
1016
import {ModuleMocker} from 'jest-mock';
1117
import {installCommonGlobals} from 'jest-util';
@@ -110,6 +116,22 @@ class NodeEnvironment implements JestEnvironment {
110116
}
111117
return null;
112118
}
119+
120+
compileFunction(code: string, params: Array<string>, filename: string) {
121+
if (this.context) {
122+
return compileFunction(code, params, {
123+
filename,
124+
parsingContext: this.context,
125+
}) as any;
126+
}
127+
return null;
128+
}
129+
}
130+
131+
// `jest-runtime` checks for `compileFunction`, so this makes sure to not expose that function if it's unsupported by this version of node
132+
// Should be removed when we drop support for node 8
133+
if (typeof compileFunction !== 'function') {
134+
delete NodeEnvironment.prototype.compileFunction;
113135
}
114136

115137
export = NodeEnvironment;

packages/jest-environment/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ export declare class JestEnvironment {
4444
fakeTimersLolex: LolexFakeTimers | null;
4545
moduleMocker: jestMock.ModuleMocker | null;
4646
runScript<T = unknown>(script: Script): T | null;
47+
compileFunction?<T = unknown>(
48+
code: string,
49+
params: Array<string>,
50+
filename: string,
51+
): T | null;
4752
setup(): Promise<void>;
4853
teardown(): Promise<void>;
4954
handleTestEvent?(event: Circus.Event, state: Circus.State): void;

packages/jest-runtime/src/index.ts

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -730,20 +730,41 @@ class Runtime {
730730
}
731731
}
732732

733-
const script = this.createScriptFromCode(transformedFile.code, filename);
733+
let compiledFunction: ModuleWrapper | null;
734+
735+
if (typeof this._environment.compileFunction === 'function') {
736+
try {
737+
compiledFunction = this._environment.compileFunction<ModuleWrapper>(
738+
transformedFile.code,
739+
this.constructInjectedModuleParameters(),
740+
filename,
741+
);
742+
} catch (e) {
743+
throw handlePotentialSyntaxError(e);
744+
}
745+
} else {
746+
const script = this.createScriptFromCode(transformedFile.code, filename);
734747

735-
const runScript = this._environment.runScript<RunScriptEvalResult>(script);
748+
const runScript = this._environment.runScript<RunScriptEvalResult>(
749+
script,
750+
);
736751

737-
if (runScript === null) {
752+
if (runScript === null) {
753+
compiledFunction = null;
754+
} else {
755+
compiledFunction = runScript[EVAL_RESULT_VARIABLE];
756+
}
757+
}
758+
759+
if (compiledFunction === null) {
738760
this._logFormattedReferenceError(
739761
'You are trying to `import` a file after the Jest environment has been torn down.',
740762
);
741763
process.exitCode = 1;
742764
return;
743765
}
744766

745-
//Wrapper
746-
runScript[EVAL_RESULT_VARIABLE].call(
767+
compiledFunction.call(
747768
localModule.exports,
748769
localModule as NodeModule, // module object
749770
localModule.exports, // module exports
@@ -1107,7 +1128,19 @@ class Runtime {
11071128
}
11081129

11091130
private wrapCodeInModuleWrapper(content: string) {
1110-
const args = [
1131+
const args = this.constructInjectedModuleParameters();
1132+
1133+
return (
1134+
'({"' +
1135+
EVAL_RESULT_VARIABLE +
1136+
`":function(${args.join(',')}){` +
1137+
content +
1138+
'\n}});'
1139+
);
1140+
}
1141+
1142+
private constructInjectedModuleParameters() {
1143+
return [
11111144
'module',
11121145
'exports',
11131146
'require',
@@ -1117,14 +1150,6 @@ class Runtime {
11171150
'jest',
11181151
...this._config.extraGlobals,
11191152
];
1120-
1121-
return (
1122-
'({"' +
1123-
EVAL_RESULT_VARIABLE +
1124-
`":function(${args.join(',')}){` +
1125-
content +
1126-
'\n}});'
1127-
);
11281153
}
11291154
}
11301155

packages/jest-transform/src/enhanceUnexpectedTokenMessage.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export default function handlePotentialSyntaxError(
1717
}
1818

1919
if (
20-
e instanceof SyntaxError &&
20+
// `instanceof` might come from the wrong context
21+
e.name === 'SyntaxError' &&
2122
(e.message.includes('Unexpected token') ||
2223
e.message.includes('Cannot use import')) &&
2324
!e.message.includes(' expected')

0 commit comments

Comments
 (0)