Skip to content

Commit 2bb6f36

Browse files
ui: Enable React compiler and fix compiler lint violations (#6289)
* ui: fix lint violations for react compiler * ui: fix react compiler compatibility issues Ensure URL state syncs correctly with the React Compiler enabled. Navbar now reads URL params via useLocation() instead of window.location.search, and Profiles opts out of memoization so URLStateProvider's sync-from-URL effect fires on navigation. * fix type error * fix lint errors * Add Babel React compiler to shared components * Ignore test files in Babel and copy static files * [pre-commit.ci lite] apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent 7ddc301 commit 2bb6f36

31 files changed

Lines changed: 3380 additions & 11610 deletions

File tree

ui/babel.react-compiler.cjs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Shared Babel config for packages that need the React compiler.
2+
// Used by shared packages to compile React components with the compiler plugin.
3+
module.exports = {
4+
ignore: [
5+
'**/*.test.ts',
6+
'**/*.test.tsx',
7+
'**/*.benchmark.ts',
8+
'**/*.benchmark.tsx',
9+
'**/benchdata/**',
10+
'**/testdata/**',
11+
'**/.DS_Store',
12+
'**/*.md',
13+
],
14+
presets: [
15+
['@babel/preset-env', {modules: false}],
16+
['@babel/preset-react', {runtime: 'automatic'}],
17+
'@babel/preset-typescript',
18+
],
19+
plugins: [
20+
[
21+
'babel-plugin-react-compiler',
22+
{
23+
target: '18',
24+
compilationMode: 'infer',
25+
},
26+
],
27+
],
28+
};

ui/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,14 @@
3030
"tailwindcss": "3.2.4"
3131
},
3232
"devDependencies": {
33+
"@babel/cli": "^7.28.6",
3334
"@babel/core": "7.29.0",
3435
"@babel/node": "7.29.0",
3536
"@babel/plugin-proposal-export-default-from": "7.27.1",
3637
"@babel/plugin-proposal-private-property-in-object": "7.21.11",
3738
"@babel/preset-env": "7.29.2",
39+
"@babel/preset-react": "^7.28.5",
40+
"@babel/preset-typescript": "^7.28.5",
3841
"@chromatic-com/storybook": "1.9.0",
3942
"@ianvs/prettier-plugin-sort-imports": "3.7.2",
4043
"@mdx-js/loader": "2.3.0",
@@ -65,6 +68,7 @@
6568
"@typescript-eslint/eslint-plugin": "5.62.0",
6669
"@typescript-eslint/parser": "5.62.0",
6770
"arg": "5.0.2",
71+
"babel-plugin-react-compiler": "1.0.0",
6872
"chromatic": "11.29.0",
6973
"css-loader": "6.11.0",
7074
"eslint": "8.45.0",
@@ -78,7 +82,7 @@
7882
"eslint-plugin-prettier": "5.5.5",
7983
"eslint-plugin-promise": "6.6.0",
8084
"eslint-plugin-react": "7.37.5",
81-
"eslint-plugin-react-hooks": "4.6.2",
85+
"eslint-plugin-react-hooks": "7.0.1",
8286
"eslint-plugin-standard": "5.0.0",
8387
"eslint-plugin-storybook": "0.12.0",
8488
"eslint-plugin-typescript-enum": "2.1.0",
@@ -98,6 +102,7 @@
98102
"postcss": "8.5.8",
99103
"prettier": "3.8.1",
100104
"prettier-plugin-tailwindcss": "^0.4.0",
105+
"react-compiler-runtime": "1.0.0",
101106
"react-is": "18.3.1",
102107
"react-test-renderer": "18.3.1",
103108
"replace-in-files": "3.0.0",

ui/packages/app/web/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,14 @@
6363
},
6464
"devDependencies": {
6565
"@types/lodash.throttle": "4.1.9",
66-
"@vitejs/plugin-react-swc": "3.11.0",
66+
"@vitejs/plugin-react": "4.7.0",
67+
"babel-plugin-react-compiler": "1.0.0",
6768
"css-loader": "6.11.0",
6869
"eslint-config-prettier": "8.10.2",
6970
"eslint-plugin-import": "2.32.0",
7071
"jest": "29.7.0",
7172
"jest-runtime": "29.7.0",
73+
"react-compiler-runtime": "1.0.0",
7274
"tslint": "6.1.3",
7375
"tslint-config-prettier": "1.18.0",
7476
"tslint-plugin-prettier": "2.3.0",

ui/packages/app/web/src/components/ui/Navbar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const Navbar = () => {
6161
const compareA = queryParams.get('compare_a');
6262
const compareB = queryParams.get('compare_b');
6363

64-
const queryParamsURL = parseParams(window.location.search);
64+
const queryParamsURL = parseParams(location.search);
6565

6666
/* eslint-disable @typescript-eslint/naming-convention */
6767
const {

ui/packages/app/web/src/pages/index.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import {useCallback} from 'react';
1515

1616
import {GrpcWebFetchTransport} from '@protobuf-ts/grpcweb-transport';
17-
import {useNavigate} from 'react-router-dom';
17+
import {useLocation, useNavigate} from 'react-router-dom';
1818

1919
import {QueryServiceClient} from '@parca/client';
2020
import {ParcaContextProvider, Spinner, URLStateProvider} from '@parca/components';
@@ -31,7 +31,12 @@ const queryClient = new QueryServiceClient(
3131
);
3232

3333
const Profiles = () => {
34+
'use no memo';
3435
const navigate = useNavigate();
36+
// useLocation() subscribes to react-router location changes so this component
37+
// re-renders on navigate(). 'use no memo' ensures the re-render propagates to
38+
// URLStateProvider, whose no-deps effect syncs state from window.location.search.
39+
useLocation();
3540
const isDarkMode = useAppSelector(selectDarkMode);
3641

3742
const navigateTo = useCallback(

ui/packages/app/web/vite.config.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,29 @@
1111
// See the License for the specific language governing permissions and
1212
// limitations under the License.
1313

14-
import react from '@vitejs/plugin-react-swc';
14+
import react from '@vitejs/plugin-react';
1515
import {defineConfig} from 'vite';
1616
import svgr from 'vite-plugin-svgr';
1717

1818
// https://vitejs.dev/config/
1919
export default defineConfig({
20-
// @ts-expect-error
21-
plugins: [react(), svgr()],
20+
// cast needed: dual @types/node versions create incompatible vite Plugin types
21+
plugins: [
22+
react({
23+
babel: {
24+
plugins: [
25+
[
26+
'babel-plugin-react-compiler',
27+
{
28+
target: '18',
29+
compilationMode: 'infer',
30+
},
31+
],
32+
],
33+
},
34+
}),
35+
svgr(),
36+
] as any,
2237
base: './',
2338
server: {
2439
port: 3000,

ui/packages/shared/components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"scripts": {
77
"test": "jest --coverage --config ../../../jest.config.js ./src/*",
88
"prepublish": "pnpm run build",
9-
"build": "tsc && tailwindcss -o dist/styles.css --minify",
9+
"build": "babel src --out-dir dist --config-file ../../../babel.react-compiler.cjs --extensions .ts,.tsx --copy-files --no-copy-ignored && tsc --emitDeclarationOnly && tailwindcss -o dist/styles.css --minify",
1010
"build-swc": "swc ./src -d dist --copy-files && tailwindcss -o dist/styles.css --minify",
1111
"watch": "tsc-watch --onCompilationComplete 'tailwindcss -o dist/styles.css'"
1212
},
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright 2022 The Parca Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
/* eslint-disable jest-dom/prefer-to-have-value */
15+
16+
import {render} from '@testing-library/react';
17+
import {describe, expect, it, vi} from 'vitest';
18+
19+
import {AbsoluteDate, DateTimeRange} from '../utils';
20+
import AbsoluteDatePicker from './index';
21+
22+
describe('AbsoluteDatePicker', () => {
23+
it('resyncs when an existing DateTimeRange instance is mutated', () => {
24+
const range = new DateTimeRange(
25+
new AbsoluteDate(new Date('2023-12-01T10:00:00Z')),
26+
new AbsoluteDate(new Date('2023-12-01T15:30:00Z'))
27+
);
28+
29+
const {rerender, getAllByRole} = render(
30+
<AbsoluteDatePicker range={range} onChange={vi.fn()} />
31+
);
32+
33+
const [startInput, endInput] = getAllByRole('textbox');
34+
expect((startInput as HTMLInputElement).value).toBe('2023-12-01 10:00:00');
35+
expect((endInput as HTMLInputElement).value).toBe('2023-12-01 15:30:00');
36+
37+
range.from = new AbsoluteDate(new Date('2023-12-02T08:15:00Z'));
38+
range.to = new AbsoluteDate(new Date('2023-12-02T09:45:00Z'));
39+
40+
rerender(<AbsoluteDatePicker range={range} onChange={vi.fn()} />);
41+
42+
expect((startInput as HTMLInputElement).value).toBe('2023-12-02 08:15:00');
43+
expect((endInput as HTMLInputElement).value).toBe('2023-12-02 09:45:00');
44+
});
45+
});

ui/packages/shared/components/src/DateTimeRangePicker/AbsoluteDatePicker/index.tsx

Lines changed: 16 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
// See the License for the specific language governing permissions and
1212
// limitations under the License.
1313

14-
import {useEffect, useMemo, useState} from 'react';
14+
import {useState} from 'react';
1515

1616
import {DateTimePicker} from '../../DateTimePicker';
1717
import {AbsoluteDate, DateTimeRange, RelativeDate, getHistoricalDate} from '../utils';
@@ -22,52 +22,24 @@ interface AbsoluteDatePickerProps {
2222
}
2323

2424
const AbsoluteDatePicker = ({range, onChange}: AbsoluteDatePickerProps): JSX.Element => {
25-
const dateFromInRelative = useMemo(() => range.from as RelativeDate, [range.from]);
26-
const dateToInRelative = useMemo(() => range.to as RelativeDate, [range.to]);
27-
28-
const [from, setFrom] = useState<AbsoluteDate>(
29-
range.from.isRelative()
30-
? new AbsoluteDate(
31-
getHistoricalDate({
32-
unit: dateFromInRelative.unit,
33-
value: dateFromInRelative.value,
34-
})
35-
)
36-
: (range.from as AbsoluteDate)
37-
);
38-
const [to, setTo] = useState<AbsoluteDate>(
39-
range.to.isRelative()
25+
const toAbsolute = (d: RelativeDate | AbsoluteDate): AbsoluteDate =>
26+
d.isRelative()
4027
? new AbsoluteDate(
41-
getHistoricalDate({
42-
unit: dateToInRelative.unit,
43-
value: dateToInRelative.value,
44-
})
28+
getHistoricalDate({unit: (d as RelativeDate).unit, value: (d as RelativeDate).value})
4529
)
46-
: (range.to as AbsoluteDate)
47-
);
30+
: (d as AbsoluteDate);
31+
32+
const [from, setFrom] = useState<AbsoluteDate>(() => toAbsolute(range.from));
33+
const [to, setTo] = useState<AbsoluteDate>(() => toAbsolute(range.to));
34+
const [prevRangeFrom, setPrevRangeFrom] = useState(range.from);
35+
const [prevRangeTo, setPrevRangeTo] = useState(range.to);
4836

49-
useEffect(() => {
50-
setFrom(
51-
range.from.isRelative()
52-
? new AbsoluteDate(
53-
getHistoricalDate({
54-
unit: dateFromInRelative.unit,
55-
value: dateFromInRelative.value,
56-
})
57-
)
58-
: (range.from as AbsoluteDate)
59-
);
60-
setTo(
61-
range.to.isRelative()
62-
? new AbsoluteDate(
63-
getHistoricalDate({
64-
unit: dateToInRelative.unit,
65-
value: dateToInRelative.value,
66-
})
67-
)
68-
: (range.to as AbsoluteDate)
69-
);
70-
}, [dateFromInRelative, dateToInRelative, range.from, range.to]);
37+
if (prevRangeFrom !== range.from || prevRangeTo !== range.to) {
38+
setPrevRangeFrom(range.from);
39+
setPrevRangeTo(range.to);
40+
setFrom(toAbsolute(range.from));
41+
setTo(toAbsolute(range.to));
42+
}
7143

7244
return (
7345
<div className="flex flex-col w-[80%] mx-auto">

ui/packages/shared/components/src/ResponsiveSvg/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
// See the License for the specific language governing permissions and
1212
// limitations under the License.
1313

14+
/* eslint-disable react-hooks/set-state-in-effect */
15+
1416
import {Children, useEffect, useState} from 'react';
1517

1618
import {useContainerDimensions} from '@parca/hooks';
@@ -33,6 +35,7 @@ const addPropsToChildren = (children: JSX.Element, props: {[x: string]: any}): J
3335
};
3436

3537
const ResponsiveSvg = (props: Props): JSX.Element => {
38+
'use no memo';
3639
const {children} = props;
3740
const {ref, dimensions} = useContainerDimensions();
3841
const {width} = dimensions ?? {width: 0};

0 commit comments

Comments
 (0)