Skip to content

Commit 65dd786

Browse files
authored
feat(builder): add source.reactCompiler to enable React Compiler (#8763)
1 parent 01c5abd commit 65dd786

20 files changed

Lines changed: 691 additions & 98 deletions

File tree

.changeset/spicy-donkeys-attack.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@modern-js/builder': minor
3+
'@modern-js/app-tools': minor
4+
---
5+
6+
feat(builder): add `source.reactCompiler` to enable React Compiler via Rspack's built-in SWC implementation
7+
feat(builder): 新增 `source.reactCompiler` 配置,基于 Rspack 内置 SWC 的 Rust 版 React Compiler 提供一键开启能力
Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
import { appTools, defineConfig } from '@modern-js/app-tools';
2-
import { pluginBabel } from '@rsbuild/plugin-babel';
32

43
// https://modernjs.dev/en/configure/app/usage
54
export default defineConfig({
6-
builderPlugins: [
7-
pluginBabel({
8-
babelLoaderOptions: (_config, { addPlugins }) => {
9-
addPlugins(['babel-plugin-react-compiler']);
10-
},
11-
}),
12-
],
5+
source: {
6+
reactCompiler: true,
7+
},
138
plugins: [appTools()],
149
});

examples/react-compiler/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,10 @@
2222
"@biomejs/biome": "1.9.4",
2323
"@modern-js/app-tools": "latest",
2424
"@modern-js/tsconfig": "latest",
25-
"@rsbuild/plugin-babel": "^1.1.2",
2625
"@types/jest": "~29.2.4",
2726
"@types/node": "^20",
2827
"@types/react": "^19.2.17",
2928
"@types/react-dom": "^19.2.3",
30-
"babel-plugin-react-compiler": "^1.0.0",
3129
"eslint-plugin-react-hooks": "^6.0.0-rc.1",
3230
"rimraf": "^6.0.1",
3331
"typescript": "~5.7.3"

packages/cli/builder/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"@rsbuild/plugin-check-syntax": "2.0.0",
3939
"@rsbuild/plugin-css-minimizer": "2.0.1",
4040
"@rsbuild/plugin-less": "1.6.4",
41-
"@rsbuild/plugin-react": "2.0.1",
41+
"@rsbuild/plugin-react": "2.1.0",
4242
"@rsbuild/plugin-rem": "1.0.6",
4343
"@rsbuild/plugin-sass": "1.5.3",
4444
"@rsbuild/plugin-source-build": "1.0.6",

packages/cli/builder/src/shared/parseCommonConfig.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,13 @@ export async function parseCommonConfig(
7070
...outputConfig
7171
} = {},
7272
html: { outputStructure, appIcon, ...htmlConfig } = {},
73-
source: { alias, globalVars, transformImport, ...sourceConfig } = {},
73+
source: {
74+
alias,
75+
globalVars,
76+
transformImport,
77+
reactCompiler,
78+
...sourceConfig
79+
} = {},
7480
dev = {},
7581
server = {},
7682
security: { checkSyntax, sri, ...securityConfig } = {},
@@ -269,7 +275,9 @@ export async function parseCommonConfig(
269275
);
270276
}
271277

272-
rsbuildPlugins.push(pluginReact());
278+
rsbuildPlugins.push(
279+
pluginReact(reactCompiler !== undefined ? { reactCompiler } : {}),
280+
);
273281

274282
if (!disableSvgr) {
275283
const { pluginSvgr } = await import('@rsbuild/plugin-svgr');

packages/cli/builder/src/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import type { PluginAssetsRetryOptions } from '@rsbuild/plugin-assets-retry';
2222
import type { PluginCheckSyntaxOptions } from '@rsbuild/plugin-check-syntax';
2323
import type { PluginCssMinimizerOptions } from '@rsbuild/plugin-css-minimizer';
2424
import type { PluginLessOptions } from '@rsbuild/plugin-less';
25+
import type { PluginReactOptions } from '@rsbuild/plugin-react';
2526
import type { PluginRemOptions } from '@rsbuild/plugin-rem';
2627
import type { PluginSassOptions } from '@rsbuild/plugin-sass';
2728
import type { PluginSourceBuildOptions } from '@rsbuild/plugin-source-build';
@@ -175,6 +176,12 @@ export type BuilderExtraConfig = {
175176
* Define global variables. It can replace expressions like `process.env.FOO` in your code after compile.
176177
*/
177178
globalVars?: ChainedGlobalVars;
179+
/**
180+
* Enable or configure React Compiler, powered by the Rust-based implementation
181+
* built into Rspack's `builtin:swc-loader` (`jsc.transform.reactCompiler`).
182+
* @default undefined (disabled)
183+
*/
184+
reactCompiler?: PluginReactOptions['reactCompiler'];
178185
};
179186
output?: {
180187
/**
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { join } from 'path';
2+
import type { Rspack } from '@rsbuild/core';
3+
import { describe, expect, test } from '@rstest/core';
4+
import { createBuilder } from '../src';
5+
import { parseCommonConfig } from '../src/shared/parseCommonConfig';
6+
import type { BuilderConfig } from '../src/types';
7+
8+
function getSwcTransformOptions(config: Rspack.Configuration) {
9+
for (const rule of config.module?.rules || []) {
10+
if (!rule || typeof rule !== 'object') {
11+
continue;
12+
}
13+
const uses = Array.isArray(rule.use) ? rule.use : [rule.use];
14+
for (const use of uses) {
15+
if (
16+
use &&
17+
typeof use === 'object' &&
18+
use.loader?.includes('swc-loader') &&
19+
typeof use.options === 'object'
20+
) {
21+
return (use.options as Rspack.SwcLoaderOptions).jsc?.transform;
22+
}
23+
}
24+
}
25+
return undefined;
26+
}
27+
28+
async function getBundlerConfig(config: BuilderConfig) {
29+
const rsbuild = await createBuilder({
30+
bundlerType: 'rspack',
31+
config,
32+
cwd: join(__dirname, '..'),
33+
});
34+
const {
35+
origin: { bundlerConfigs },
36+
} = await rsbuild.inspectConfig();
37+
return bundlerConfigs[0];
38+
}
39+
40+
describe('source.reactCompiler', () => {
41+
test('should not enable react compiler by default', async () => {
42+
const transform = getSwcTransformOptions(await getBundlerConfig({}));
43+
44+
expect(transform).toBeDefined();
45+
expect(transform).not.toHaveProperty('reactCompiler');
46+
});
47+
48+
test('should enable react compiler when set to true', async () => {
49+
const transform = getSwcTransformOptions(
50+
await getBundlerConfig({
51+
source: {
52+
reactCompiler: true,
53+
},
54+
}),
55+
);
56+
57+
expect(transform?.reactCompiler).toEqual(true);
58+
});
59+
60+
test('should pass through react compiler options object', async () => {
61+
const transform = getSwcTransformOptions(
62+
await getBundlerConfig({
63+
source: {
64+
reactCompiler: {
65+
target: '18',
66+
compilationMode: 'annotation',
67+
},
68+
},
69+
}),
70+
);
71+
72+
expect(transform?.reactCompiler).toEqual({
73+
target: '18',
74+
compilationMode: 'annotation',
75+
});
76+
});
77+
78+
test('should not leak reactCompiler into rsbuild source config', async () => {
79+
const { rsbuildConfig } = await parseCommonConfig({
80+
source: {
81+
reactCompiler: true,
82+
},
83+
});
84+
85+
expect(rsbuildConfig.source).not.toHaveProperty('reactCompiler');
86+
});
87+
});
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
title: reactCompiler
3+
---
4+
5+
# source.reactCompiler
6+
7+
- **Type:** `boolean | ReactCompilerOptions`
8+
- **Default:** `undefined` (disabled)
9+
10+
Whether to enable [React Compiler](https://react.dev/learn/react-compiler). React Compiler is a build-time tool that optimizes re-rendering performance of React applications through automatic memoization.
11+
12+
Modern.js implements this capability based on the Rust-based React Compiler built into Rspack's `builtin:swc-loader` (equivalent to setting SWC's `jsc.transform.reactCompiler`), reusing Rspack's built-in SWC transform chain without introducing Babel.
13+
14+
:::tip
15+
This option is disabled by default. It must be enabled explicitly for any React version, including React 19.
16+
:::
17+
18+
## Example
19+
20+
### Enable React Compiler (React 19)
21+
22+
```ts title="modern.config.ts"
23+
import { defineConfig } from '@modern-js/app-tools';
24+
25+
export default defineConfig({
26+
source: {
27+
reactCompiler: true,
28+
},
29+
});
30+
```
31+
32+
### Using with React 18
33+
34+
The compiled output targets React 19 by default. To use it in React 18 projects, you need to:
35+
36+
1. Install [react-compiler-runtime](https://www.npmjs.com/package/react-compiler-runtime) as a **runtime dependency** (the compiled output references it at runtime):
37+
38+
```bash
39+
npm add react-compiler-runtime
40+
```
41+
42+
2. Specify the React version via `target`:
43+
44+
```ts title="modern.config.ts"
45+
import { defineConfig } from '@modern-js/app-tools';
46+
47+
export default defineConfig({
48+
source: {
49+
reactCompiler: {
50+
target: '18',
51+
},
52+
},
53+
});
54+
```
55+
56+
### Customize compilation behavior
57+
58+
When passing an object, the options are the same as Rspack's `jsc.transform.reactCompiler`. For example, use `compilationMode: 'annotation'` to only compile functions annotated with the `"use memo"` directive:
59+
60+
```ts title="modern.config.ts"
61+
import { defineConfig } from '@modern-js/app-tools';
62+
63+
export default defineConfig({
64+
source: {
65+
reactCompiler: {
66+
compilationMode: 'annotation',
67+
},
68+
},
69+
});
70+
```
71+
72+
For the complete list of options, see [Rsbuild - reactCompiler](https://rsbuild.rs/plugins/list/plugin-react#reactcompiler) and the [React Compiler configuration docs](https://react.dev/reference/react-compiler/configuration).
Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,69 @@
11
# React Compiler
22

3-
The React Compiler is an experimental compiler introduced in React 19 that can automatically optimize your React code.
3+
React Compiler is a build-time compiler from the React team that reduces unnecessary re-renders through automatic memoization, without manually writing `useMemo`, `useCallback`, or `React.memo`.
44

5-
Before starting to use the React Compiler, it is recommended to read the [React Compiler documentation](https://zh-hans.react.dev/learn/react-compiler) to understand its features, current status, and usage.
5+
Before starting to use React Compiler, it is recommended to read the [React Compiler documentation](https://react.dev/learn/react-compiler) to understand its features, current status, and usage.
66

77
## How to Use
88

9-
### React 19
9+
Modern.js provides built-in support for React Compiler via the [source.reactCompiler](/configure/app/source/react-compiler) option, powered by the Rust-based React Compiler implementation in Rspack's `builtin:swc-loader`. It reuses Rspack's built-in SWC transform chain without introducing Babel (`babel-plugin-react-compiler`).
1010

11-
If you are using React 19, Modern.js has built-in support for React Compiler, and no additional configuration is required.
11+
This capability is **disabled by default** and must be enabled explicitly for any React version.
1212

13-
### React 18
13+
### React 19
1414

15-
If you are using React 18, you need to configure it as follows:
15+
If you are using React 19, simply enable `source.reactCompiler` — no additional dependencies are required:
1616

17-
1. Install `react-compiler-runtime` to allow the compiled code to run on versions before 19:
17+
```ts title="modern.config.ts"
18+
import { appTools, defineConfig } from '@modern-js/app-tools';
1819

19-
```bash
20-
npm install react-compiler-runtime
20+
export default defineConfig({
21+
source: {
22+
reactCompiler: true,
23+
},
24+
plugins: [appTools()],
25+
});
2126
```
2227

23-
2. Install `babel-plugin-react-compiler`:
28+
### React 18
29+
30+
If you are using React 18, configure it as follows:
31+
32+
1. Install `react-compiler-runtime` as a **runtime dependency** to allow the compiled code to run on versions before React 19:
2433

2534
```bash
26-
npm install babel-plugin-react-compiler
35+
npm add react-compiler-runtime
2736
```
2837

29-
3. Register the Babel plugin in your Modern.js configuration file:
38+
2. Specify the React version via `target` in the configuration:
3039

3140
```ts title="modern.config.ts"
3241
import { appTools, defineConfig } from '@modern-js/app-tools';
33-
import { pluginBabel } from '@rsbuild/plugin-babel';
3442

3543
export default defineConfig({
36-
builderPlugins: [
37-
pluginBabel({
38-
babelLoaderOptions: (config, { addPlugins }) => {
39-
addPlugins([
40-
[
41-
'babel-plugin-react-compiler',
42-
{
43-
target: '18', // 或 '17',根据你使用的 React 版本
44-
},
45-
],
46-
]);
47-
},
48-
});
49-
];
44+
source: {
45+
reactCompiler: {
46+
target: '18',
47+
},
48+
},
5049
plugins: [appTools()],
5150
});
5251
```
5352

53+
## Customize Compilation Behavior
54+
55+
`source.reactCompiler` accepts an object to configure the compilation behavior, with the same options as Rspack's `jsc.transform.reactCompiler`. For example, use `compilationMode: 'annotation'` to only compile functions annotated with the `"use memo"` directive, enabling React Compiler incrementally:
56+
57+
```ts title="modern.config.ts"
58+
export default defineConfig({
59+
source: {
60+
reactCompiler: {
61+
compilationMode: 'annotation',
62+
},
63+
},
64+
});
65+
```
66+
67+
For the complete list of options, see [Rsbuild - reactCompiler](https://rsbuild.rs/plugins/list/plugin-react#reactcompiler) and the [React Compiler configuration docs](https://react.dev/reference/react-compiler/configuration).
68+
5469
> For detailed code, you can refer to the [Modern.js & React Compiler example project](https://github.com/web-infra-dev/modern.js/tree/main/examples/react-compiler)

0 commit comments

Comments
 (0)