Skip to content

Commit a66eec7

Browse files
authored
chore(jest-transform): refactor transformer API to reduce number of arguments (#10834)
1 parent 29156d1 commit a66eec7

File tree

27 files changed

+377
-191
lines changed

27 files changed

+377
-191
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
- `[jest-runtime]` [**BREAKING**] remove long-deprecated `jest.addMatchers`, `jest.resetModuleRegistry`, and `jest.runTimersToTime` ([#9853](https://github.com/facebook/jest/pull/9853))
2020
- `[jest-transform]` Show enhanced `SyntaxError` message for all `SyntaxError`s ([#10749](https://github.com/facebook/jest/pull/10749))
2121
- `[jest-transform]` [**BREAKING**] Refactor API to pass an options bag around rather than multiple boolean options ([#10753](https://github.com/facebook/jest/pull/10753))
22+
- `[jest-transform]` [**BREAKING**] Refactor API of transformers to pass an options bag rather than separate `config` and other options
2223

2324
### Chore & Maintenance
2425

docs/CodeTransformation.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
---
2+
id: code-transformation
3+
title: Code Transformation
4+
---
5+
6+
Jest runs the code in your project as JavaScript, but if you use some syntax not supported by Node.js out of the box (such as JSX, types from TypeScript, Vue templates etc.) then you'll need to transform that code into plain JavaScript, similar to what you would do when building for browsers.
7+
8+
Jest supports this via the [`transform` configuration option](Configuration.md#transform-objectstring-pathtotransformer--pathtotransformer-object).
9+
10+
A transformer is a module that provides a synchronous function for transforming source files. For example, if you wanted to be able to use a new language feature in your modules or tests that aren't yet supported by Node, you might plug in one of many compilers that compile a future version of JavaScript to a current one.
11+
12+
Jest will cache the result of a transformation and attempt to invalidate that result based on a number of factors, such as the source of the file being transformed and changing configuration.
13+
14+
## Defaults
15+
16+
Jest ships with one transformer out of the box - `babel-jest`. It will automatically load your project's Babel configuration and transform any file matching the following RegEx: `/\.[jt]sx?$/` meaning any `.js`, `.jsx`, `.ts` and `.tsx` file. In addition, `babel-jest` will inject the Babel plugin necessary for mock hoisting talked about in [ES Module mocking](ManualMocks.md#using-with-es-module-imports).
17+
18+
If you override the `transform` configuration option `babel-jest` will no longer be active, and you'll need to add it manually if you wish to use Babel.
19+
20+
## Writing custom transformers
21+
22+
You can write you own transformer. The API of a transformer is as follows:
23+
24+
```ts
25+
interface Transformer<OptionType = unknown> {
26+
/**
27+
* Indicates if the transformer is capabale of instrumenting the code for code coverage.
28+
*
29+
* If V8 coverage is _not_ active, and this is `true`, Jest will assume the code is instrumented.
30+
* If V8 coverage is _not_ active, and this is `false`. Jest will instrument the code returned by this transformer using Babel.
31+
*/
32+
canInstrument?: boolean;
33+
createTransformer?: (options?: OptionType) => Transformer;
34+
35+
getCacheKey?: (
36+
sourceText: string,
37+
sourcePath: string,
38+
options: TransformOptions,
39+
) => string;
40+
41+
process: (
42+
sourceText: string,
43+
sourcePath: string,
44+
options: TransformOptions,
45+
) => TransformedSource;
46+
}
47+
48+
interface TransformOptions {
49+
config: Config.ProjectConfig;
50+
/** A stringified version of the configuration - useful in cache busting */
51+
configString: string;
52+
instrument: boolean;
53+
// names are copied from babel: https://babeljs.io/docs/en/options#caller
54+
supportsDynamicImport: boolean;
55+
supportsExportNamespaceFrom: boolean;
56+
supportsStaticESM: boolean;
57+
supportsTopLevelAwait: boolean;
58+
}
59+
60+
type TransformedSource =
61+
| {code: string; map?: RawSourceMap | string | null}
62+
| string;
63+
64+
// Config.ProjectConfig can be seen in in code [here](https://github.com/facebook/jest/blob/v26.6.3/packages/jest-types/src/Config.ts#L323)
65+
// RawSourceMap comes from [`source-map`](https://github.com/mozilla/source-map/blob/0.6.1/source-map.d.ts#L6-L12)
66+
```
67+
68+
As can be seen, only `process` is mandatory to implement, although we highly recommend implementing `getCacheKey` as well, so we don't waste resources transpiling the same source file when we can read its previous result from disk. You can use [`@jest/create-cache-key-function`](https://www.npmjs.com/package/@jest/create-cache-key-function) to help implement it.
69+
70+
Note that [ECMAScript module](ECMAScriptModules.md) support is indicated by the passed in `supports*` options. Specifically `supportsDynamicImport: true` means the transformer can return `import()` expressions, which is supported by both ESM and CJS. If `supportsStaticESM: true` it means top level `import` statements are supported and the code will be interpreted as ESM and not CJS. See [Node's docs](https://nodejs.org/api/esm.html#esm_differences_between_es_modules_and_commonjs) for details on the differences.
71+
72+
### Examples
73+
74+
### TypeScript with type checking
75+
76+
While `babel-jest` by default will transpile TypeScript files, Babel will not verify the types. If you want that you can use [`ts-jest`](https://github.com/kulshekhar/ts-jest).
77+
78+
#### Transforming images to their path
79+
80+
Importing images is a way to include them in your browser bundle, but they are not valid JavaScript. One way of handling it in Jest is to replace the imported value with its filename.
81+
82+
```js
83+
// fileTransformer.js
84+
const path = require('path');
85+
86+
module.exports = {
87+
process(src, filename, config, options) {
88+
return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';';
89+
},
90+
};
91+
```
92+
93+
```js
94+
// jest.config.js
95+
96+
module.exports = {
97+
transform: {
98+
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
99+
'<rootDir>/fileTransformer.js',
100+
},
101+
};
102+
```

docs/Configuration.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,8 +1263,7 @@ Examples of such compilers include:
12631263

12641264
- [Babel](https://babeljs.io/)
12651265
- [TypeScript](http://www.typescriptlang.org/)
1266-
- [async-to-gen](http://github.com/leebyron/async-to-gen#jest)
1267-
- To build your own please visit the [Custom Transformer](TutorialReact.md#custom-transformers) section
1266+
- To build your own please visit the [Custom Transformer](CodeTransformation.md#writing-custom-transformers) section
12681267

12691268
You can pass configuration to a transformer like `{filePattern: ['path-to-transformer', {options}]}` For example, to configure babel-jest for non-default behavior, `{"\\.js$": ['babel-jest', {rootMode: "upward"}]}`
12701269

docs/TutorialReact.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ The code for this example is available at [examples/enzyme](https://github.com/f
304304
305305
### Custom transformers
306306
307-
If you need more advanced functionality, you can also build your own transformer. Instead of using babel-jest, here is an example of using babel:
307+
If you need more advanced functionality, you can also build your own transformer. Instead of using `babel-jest`, here is an example of using `@babel/core`:
308308
309309
```javascript
310310
// custom-transformer.js
@@ -320,7 +320,7 @@ module.exports = {
320320
presets: [jestPreset],
321321
});
322322

323-
return result ? result.code : src;
323+
return result || src;
324324
},
325325
};
326326
```
@@ -329,7 +329,7 @@ Don't forget to install the `@babel/core` and `babel-preset-jest` packages for t
329329
330330
To make this work with Jest you need to update your Jest configuration with this: `"transform": {"\\.js$": "path/to/custom-transformer.js"}`.
331331
332-
If you'd like to build a transformer with babel support, you can also use babel-jest to compose one and pass in your custom configuration options:
332+
If you'd like to build a transformer with babel support, you can also use `babel-jest` to compose one and pass in your custom configuration options:
333333
334334
```javascript
335335
const babelJest = require('babel-jest');
@@ -338,3 +338,5 @@ module.exports = babelJest.createTransformer({
338338
presets: ['my-custom-preset'],
339339
});
340340
```
341+
342+
See [dedicated docs](CodeTransformation.md#writing-custom-transformers) for more details.

e2e/coverage-transform-instrumented/preprocessor.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,16 @@ const options = {
1818

1919
module.exports = {
2020
canInstrument: true,
21-
process(src, filename, config, transformOptions) {
21+
process(src, filename, transformOptions) {
2222
options.filename = filename;
2323

24-
if (transformOptions && transformOptions.instrument) {
24+
if (transformOptions.instrument) {
2525
options.auxiliaryCommentBefore = ' istanbul ignore next ';
2626
options.plugins = [
2727
[
2828
babelIstanbulPlugin,
2929
{
30-
cwd: config.rootDir,
30+
cwd: transformOptions.config.rootDir,
3131
exclude: [],
3232
},
3333
],

e2e/snapshot-serializers/transformer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
'use strict';
99

1010
module.exports = {
11-
process(src, filename, config, options) {
11+
process(src, filename) {
1212
if (/bar.js$/.test(filename)) {
1313
return `${src};\nmodule.exports = createPlugin('bar');`;
1414
}

e2e/transform/custom-instrumenting-preprocessor/preprocessor.js

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

88
module.exports = {
99
canInstrument: true,
10-
process(src, filename, config, options) {
10+
process(src, filename, options) {
1111
src = `${src};\nglobal.__PREPROCESSED__ = true;`;
1212

1313
if (options.instrument) {

e2e/transform/multiple-transformers/cssPreprocessor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
module.exports = {
9-
process(src, filename, config, options) {
9+
process() {
1010
return `
1111
module.exports = {
1212
root: 'App-root',

e2e/transform/multiple-transformers/filePreprocessor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
const path = require('path');
99

1010
module.exports = {
11-
process(src, filename, config, options) {
11+
process(src, filename) {
1212
return `
1313
module.exports = '${path.basename(filename)}';
1414
`;

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@
144144
},
145145
"resolutions": {
146146
"@types/jest/jest-diff": "^25.1.0",
147-
"@types/jest/pretty-format": "^25.1.0"
147+
"@types/jest/pretty-format": "^25.1.0",
148+
"fbjs-scripts": "patch:fbjs-scripts@^1.1.0#./patches/fbjs-scripts.patch"
148149
}
149150
}

0 commit comments

Comments
 (0)