Skip to content

Commit 8c939b5

Browse files
authored
feat(plugin-react): enable support for profiling React in prod env (#2143)
1 parent 2c397f0 commit 8c939b5

File tree

7 files changed

+101
-5
lines changed

7 files changed

+101
-5
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { test, expect } from '@playwright/test';
2+
import { build, gotoPage } from '@e2e/helper';
3+
4+
const fixtures = __dirname;
5+
6+
test('should render element with enabled profiler correctly', async ({
7+
page,
8+
}) => {
9+
const rsbuild = await build({
10+
cwd: fixtures,
11+
runServer: true,
12+
});
13+
14+
await gotoPage(page, rsbuild);
15+
16+
const testEl = page.locator('#test');
17+
await expect(testEl).toHaveText('Hello Rsbuild!');
18+
19+
await rsbuild.close();
20+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { defineConfig } from '@rsbuild/core';
2+
import { pluginReact } from '@rsbuild/plugin-react';
3+
4+
export default defineConfig({
5+
plugins: [
6+
pluginReact({
7+
enableProfiler: true,
8+
}),
9+
],
10+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import React from 'react';
2+
3+
const App = () => <div id="test">Hello Rsbuild!</div>;
4+
5+
export default App;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import React from 'react';
2+
import { createRoot } from 'react-dom/client';
3+
import App from './App';
4+
5+
const container = document.getElementById('root');
6+
if (container) {
7+
const root = createRoot(container);
8+
root.render(React.createElement(App));
9+
}

packages/plugin-react/src/index.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { RsbuildPlugin } from '@rsbuild/core';
22
import type { SwcReactConfig } from '@rsbuild/shared';
33
import { applySplitChunksRule } from './splitChunks';
4-
import { applyBasicReactSupport } from './react';
4+
import { applyBasicReactSupport, applyReactProfiler } from './react';
5+
import { getNodeEnv } from '@rsbuild/shared';
56

67
export type SplitReactChunkOptions = {
78
/**
@@ -28,18 +29,26 @@ export type PluginReactOptions = {
2829
* Configuration for chunk splitting of React-related dependencies.
2930
*/
3031
splitChunks?: SplitReactChunkOptions;
32+
enableProfiler?: boolean;
3133
};
3234

3335
export const PLUGIN_REACT_NAME = 'rsbuild:react';
3436

35-
export const pluginReact = (
36-
options: PluginReactOptions = {},
37-
): RsbuildPlugin => ({
37+
export const pluginReact = ({
38+
enableProfiler = false,
39+
...options
40+
}: PluginReactOptions = {}): RsbuildPlugin => ({
3841
name: PLUGIN_REACT_NAME,
3942

4043
setup(api) {
44+
const isEnvProductionProfile =
45+
enableProfiler && getNodeEnv() === 'production';
4146
if (api.context.bundlerType === 'rspack') {
4247
applyBasicReactSupport(api, options);
48+
49+
if (isEnvProductionProfile) {
50+
applyReactProfiler(api);
51+
}
4352
}
4453

4554
applySplitChunksRule(api, options?.splitChunks);

packages/plugin-react/src/react.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
modifySwcLoaderOptions,
66
type SwcReactConfig,
77
} from '@rsbuild/shared';
8-
import type { RsbuildPluginAPI } from '@rsbuild/core';
8+
import type { RsbuildConfig, RsbuildPluginAPI } from '@rsbuild/core';
99
import type { PluginReactOptions } from '.';
1010

1111
export const applyBasicReactSupport = (
@@ -52,3 +52,29 @@ export const applyBasicReactSupport = (
5252
.use(ReactRefreshRspackPlugin, [{ include: [SCRIPT_REGEX] }]);
5353
});
5454
};
55+
56+
export const applyReactProfiler = (api: RsbuildPluginAPI) => {
57+
api.modifyRsbuildConfig((config, { mergeRsbuildConfig }) => {
58+
const enableProfilerConfig: RsbuildConfig = {
59+
output: {
60+
minify: {
61+
jsOptions: {
62+
// Need to keep classnames and function names like Components for debugging purposes.
63+
mangle: {
64+
keep_classnames: true,
65+
keep_fnames: true,
66+
},
67+
},
68+
},
69+
},
70+
};
71+
return mergeRsbuildConfig(config, enableProfilerConfig);
72+
});
73+
74+
api.modifyBundlerChain((chain) => {
75+
// Replace react-dom with the profiling version.
76+
// Reference: https://gist.github.com/bvaughn/25e6233aeb1b4f0cdb8d8366e54a3977
77+
chain.resolve.alias.set('react-dom$', 'react-dom/profiling');
78+
chain.resolve.alias.set('scheduler/tracing', 'scheduler/tracing-profiling');
79+
});
80+
};

website/docs/en/plugins/list/plugin-react.mdx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,20 @@ pluginReact({
145145
},
146146
});
147147
```
148+
149+
### enableProfiler
150+
151+
When set to true, enables the React Profiler for performance analysis in production builds. Use the React DevTools to examine profiling results and identify potential performance optimizations. Profiling adds a slight overhead, so it's disabled by default in production environments.
152+
153+
```ts title="rsbuild.config.ts"
154+
pluginReact({
155+
// Only enable the profiler when REACT_PROFILER is true, as the option will increase the build time and adds some small additional overhead.
156+
enableProfiler: process.env.REACT_PROFILER === 'true',
157+
});
158+
```
159+
160+
Set `REACT_PROFILER` when running build script.
161+
162+
```
163+
REACT_PROFILER=true npx rsbuild build
164+
```

0 commit comments

Comments
 (0)