Skip to content

Commit dd13096

Browse files
authored
feat(runtime): add support for async code transformation (#11191)
1 parent 858c50b commit dd13096

File tree

9 files changed

+122
-11
lines changed

9 files changed

+122
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
- `[jest-runner]` [**BREAKING**] Run transforms over `runnner` ([#8823](https://github.com/facebook/jest/pull/8823))
2020
- `[jest-runner]` [**BREAKING**] Run transforms over `testRunnner` ([#8823](https://github.com/facebook/jest/pull/8823))
2121
- `[jest-runtime, jest-transform]` share `cacheFS` between runtime and transformer ([#10901](https://github.com/facebook/jest/pull/10901))
22+
- `[jest-runtime]` Support for async code transformations ([#11191](https://github.com/facebook/jest/pull/11191))
2223
- `[jest-reporters]` Add static filepath property to all reporters ([#11015](https://github.com/facebook/jest/pull/11015))
2324
- `[jest-snapshot]` [**BREAKING**] Make prettier optional for inline snapshots - fall back to string replacement ([#7792](https://github.com/facebook/jest/pull/7792))
2425
- `[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))

docs/Configuration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,6 +1299,8 @@ _Note: when adding additional code transformers, this will overwrite the default
12991299

13001300
A transformer must be an object with at least a `process` function, and it's also recommended to include a `getCacheKey` function. If your transformer is written in ESM you should have a default export with that object.
13011301

1302+
If the tests are written using [native ESM](ECMAScriptModules.md) the transformer can export `processAsync` and `getCacheKeyAsync` instead or in addition to the synchronous variants.
1303+
13021304
### `transformIgnorePatterns` \[array<string>]
13031305

13041306
Default: `["/node_modules/", "\\.pnp\\.[^\\\/]+$"]`

e2e/__tests__/transform.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,4 +251,17 @@ onNodeVersions('^12.17.0 || >=13.2.0', () => {
251251
expect(json.numPassedTests).toBe(1);
252252
});
253253
});
254+
255+
describe('async-transformer', () => {
256+
const dir = path.resolve(__dirname, '../transform/async-transformer');
257+
258+
it('should transform with transformer with only async transforms', () => {
259+
const {json, stderr} = runWithJson(dir, ['--no-cache'], {
260+
nodeOptions: '--experimental-vm-modules',
261+
});
262+
expect(stderr).toMatch(/PASS/);
263+
expect(json.success).toBe(true);
264+
expect(json.numPassedTests).toBe(1);
265+
});
266+
});
254267
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import m from '../module-under-test';
9+
10+
test('ESM transformer intercepts', () => {
11+
expect(m).toEqual(42);
12+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
export default 'It was not transformed!!';
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
const fileToTransform = require.resolve('./module-under-test');
11+
12+
module.exports = {
13+
async processAsync(src, filepath) {
14+
if (filepath !== fileToTransform) {
15+
throw new Error(`Unsupported filepath ${filepath}`);
16+
}
17+
18+
return 'export default 42;';
19+
},
20+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "module",
3+
"jest": {
4+
"testEnvironment": "node",
5+
"transform": {
6+
"module-under-test\\.js$": "<rootDir>/my-transform.cjs"
7+
}
8+
}
9+
}

packages/jest-runtime/src/index.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
CallerTransformOptions,
3737
ScriptTransformer,
3838
ShouldInstrumentOptions,
39+
TransformResult,
3940
TransformationOptions,
4041
handlePotentialSyntaxError,
4142
shouldInstrument,
@@ -389,7 +390,7 @@ export default class Runtime {
389390
return core;
390391
}
391392

392-
const transformedCode = this.transformFile(modulePath, {
393+
const transformedCode = await this.transformFileAsync(modulePath, {
393394
isInternalModule: false,
394395
supportsDynamicImport: true,
395396
supportsExportNamespaceFrom: true,
@@ -1182,7 +1183,50 @@ export default class Runtime {
11821183
return source;
11831184
}
11841185

1185-
const transformedFile = this._scriptTransformer.transform(
1186+
let transformedFile: TransformResult | undefined = this._fileTransforms.get(
1187+
filename,
1188+
);
1189+
1190+
if (transformedFile) {
1191+
return transformedFile.code;
1192+
}
1193+
1194+
transformedFile = this._scriptTransformer.transform(
1195+
filename,
1196+
this._getFullTransformationOptions(options),
1197+
source,
1198+
);
1199+
1200+
this._fileTransforms.set(filename, {
1201+
...transformedFile,
1202+
wrapperLength: this.constructModuleWrapperStart().length,
1203+
});
1204+
1205+
if (transformedFile.sourceMapPath) {
1206+
this._sourceMapRegistry.set(filename, transformedFile.sourceMapPath);
1207+
}
1208+
return transformedFile.code;
1209+
}
1210+
1211+
private async transformFileAsync(
1212+
filename: string,
1213+
options?: InternalModuleOptions,
1214+
): Promise<string> {
1215+
const source = this.readFile(filename);
1216+
1217+
if (options?.isInternalModule) {
1218+
return source;
1219+
}
1220+
1221+
let transformedFile: TransformResult | undefined = this._fileTransforms.get(
1222+
filename,
1223+
);
1224+
1225+
if (transformedFile) {
1226+
return transformedFile.code;
1227+
}
1228+
1229+
transformedFile = await this._scriptTransformer.transformAsync(
11861230
filename,
11871231
this._getFullTransformationOptions(options),
11881232
source,

packages/jest-transform/src/types.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export interface TransformOptions<OptionType = unknown>
6565

6666
export interface SyncTransformer<OptionType = unknown> {
6767
canInstrument?: boolean;
68-
createTransformer?: (options?: OptionType) => SyncTransformer;
68+
createTransformer?: (options?: OptionType) => SyncTransformer<OptionType>;
6969

7070
getCacheKey?: (
7171
sourceText: string,
@@ -76,7 +76,7 @@ export interface SyncTransformer<OptionType = unknown> {
7676
getCacheKeyAsync?: (
7777
sourceText: string,
7878
sourcePath: Config.Path,
79-
options: TransformOptions,
79+
options: TransformOptions<OptionType>,
8080
) => Promise<string>;
8181

8282
process: (
@@ -88,37 +88,39 @@ export interface SyncTransformer<OptionType = unknown> {
8888
processAsync?: (
8989
sourceText: string,
9090
sourcePath: Config.Path,
91-
options?: TransformOptions,
91+
options: TransformOptions<OptionType>,
9292
) => Promise<TransformedSource>;
9393
}
9494

9595
export interface AsyncTransformer<OptionType = unknown> {
9696
canInstrument?: boolean;
97-
createTransformer?: (options?: OptionType) => AsyncTransformer;
97+
createTransformer?: (options?: OptionType) => AsyncTransformer<OptionType>;
9898

9999
getCacheKey?: (
100100
sourceText: string,
101101
sourcePath: Config.Path,
102-
options: TransformOptions,
102+
options: TransformOptions<OptionType>,
103103
) => string;
104104

105105
getCacheKeyAsync?: (
106106
sourceText: string,
107107
sourcePath: Config.Path,
108-
options: TransformOptions,
108+
options: TransformOptions<OptionType>,
109109
) => Promise<string>;
110110

111111
process?: (
112112
sourceText: string,
113113
sourcePath: Config.Path,
114-
options?: TransformOptions,
114+
options: TransformOptions<OptionType>,
115115
) => TransformedSource;
116116

117117
processAsync: (
118118
sourceText: string,
119119
sourcePath: Config.Path,
120-
options?: TransformOptions,
120+
options: TransformOptions<OptionType>,
121121
) => Promise<TransformedSource>;
122122
}
123123

124-
export type Transformer = SyncTransformer | AsyncTransformer;
124+
export type Transformer<OptionType = unknown> =
125+
| SyncTransformer<OptionType>
126+
| AsyncTransformer<OptionType>;

0 commit comments

Comments
 (0)