Skip to content

Commit e7d213d

Browse files
authored
feat[react-devtools]: display forget badge for components in profiling session (#29014)
# Summary - `compiledWithForget` field for nodes is now propagated from the backend to frontend profiler stores - Corresponding node with such field will have a `✨` prefix displayed before its displayName <img width="1728" alt="Screenshot 2024-05-07 at 15 05 37" src="https://github.com/facebook/react/assets/28902667/fe044d40-52cb-4169-867d-5a2d72e3275b"> - Badges are now displayed on the right panel when some fiber is selected in a specific commit <img width="1728" alt="Screenshot 2024-05-07 at 15 05 50" src="https://github.com/facebook/react/assets/28902667/297ba5ca-404d-4172-b9bf-bfed7978afe5"> - Badges are also displayed when user hovers over some node in the tree <img width="1728" alt="Screenshot 2024-05-07 at 15 25 22" src="https://github.com/facebook/react/assets/28902667/bee47884-61d1-46b6-a483-717fc148893a">
1 parent c32ff0f commit e7d213d

16 files changed

+170
-68
lines changed

packages/react-devtools-shared/src/__tests__/profilingCharts-test.js

+18-18
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,8 @@ describe('profiling charts', () => {
124124
"actualDuration": 0,
125125
"didRender": true,
126126
"id": 5,
127-
"label": "Memo(Child) key="third" (<0.1ms of <0.1ms)",
128-
"name": "Memo(Child)",
127+
"label": "Child (Memo) key="third" (<0.1ms of <0.1ms)",
128+
"name": "Child",
129129
"offset": 15,
130130
"selfDuration": 0,
131131
"treeBaseDuration": 0,
@@ -134,8 +134,8 @@ describe('profiling charts', () => {
134134
"actualDuration": 2,
135135
"didRender": true,
136136
"id": 4,
137-
"label": "Memo(Child) key="second" (2ms of 2ms)",
138-
"name": "Memo(Child)",
137+
"label": "Child (Memo) key="second" (2ms of 2ms)",
138+
"name": "Child",
139139
"offset": 13,
140140
"selfDuration": 2,
141141
"treeBaseDuration": 2,
@@ -144,8 +144,8 @@ describe('profiling charts', () => {
144144
"actualDuration": 3,
145145
"didRender": true,
146146
"id": 3,
147-
"label": "Memo(Child) key="first" (3ms of 3ms)",
148-
"name": "Memo(Child)",
147+
"label": "Child (Memo) key="first" (3ms of 3ms)",
148+
"name": "Child",
149149
"offset": 10,
150150
"selfDuration": 3,
151151
"treeBaseDuration": 3,
@@ -175,8 +175,8 @@ describe('profiling charts', () => {
175175
"actualDuration": 0,
176176
"didRender": false,
177177
"id": 5,
178-
"label": "Memo(Child) key="third"",
179-
"name": "Memo(Child)",
178+
"label": "Child (Memo) key="third"",
179+
"name": "Child",
180180
"offset": 15,
181181
"selfDuration": 0,
182182
"treeBaseDuration": 0,
@@ -185,8 +185,8 @@ describe('profiling charts', () => {
185185
"actualDuration": 0,
186186
"didRender": false,
187187
"id": 4,
188-
"label": "Memo(Child) key="second"",
189-
"name": "Memo(Child)",
188+
"label": "Child (Memo) key="second"",
189+
"name": "Child",
190190
"offset": 13,
191191
"selfDuration": 0,
192192
"treeBaseDuration": 2,
@@ -195,8 +195,8 @@ describe('profiling charts', () => {
195195
"actualDuration": 0,
196196
"didRender": false,
197197
"id": 3,
198-
"label": "Memo(Child) key="first"",
199-
"name": "Memo(Child)",
198+
"label": "Child (Memo) key="first"",
199+
"name": "Child",
200200
"offset": 10,
201201
"selfDuration": 0,
202202
"treeBaseDuration": 3,
@@ -264,20 +264,20 @@ describe('profiling charts', () => {
264264
},
265265
{
266266
"id": 3,
267-
"label": "Memo(Child) (Memo) key="first" (3ms)",
268-
"name": "Memo(Child)",
267+
"label": "Child (Memo) key="first" (3ms)",
268+
"name": "Child",
269269
"value": 3,
270270
},
271271
{
272272
"id": 4,
273-
"label": "Memo(Child) (Memo) key="second" (2ms)",
274-
"name": "Memo(Child)",
273+
"label": "Child (Memo) key="second" (2ms)",
274+
"name": "Child",
275275
"value": 2,
276276
},
277277
{
278278
"id": 5,
279-
"label": "Memo(Child) (Memo) key="third" (<0.1ms)",
280-
"name": "Memo(Child)",
279+
"label": "Child (Memo) key="third" (<0.1ms)",
280+
"name": "Child",
281281
"value": 0,
282282
},
283283
]

packages/react-devtools-shared/src/devtools/ProfilerStore.js

+1
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ export default class ProfilerStore extends EventEmitter<{
214214
hocDisplayNames: element.hocDisplayNames,
215215
key: element.key,
216216
type: element.type,
217+
compiledWithForget: element.compiledWithForget,
217218
};
218219
profilingSnapshots.set(elementID, snapshotNode);
219220

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementBadges.css

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
.Root {
2-
padding: 0.25rem;
32
user-select: none;
43
display: inline-flex;
54
}

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementBadges.js

+12-5
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
* @flow
88
*/
99

10-
import type {Element} from 'react-devtools-shared/src/frontend/types';
11-
1210
import * as React from 'react';
1311

1412
import Badge from './Badge';
@@ -17,11 +15,20 @@ import ForgetBadge from './ForgetBadge';
1715
import styles from './InspectedElementBadges.css';
1816

1917
type Props = {
20-
element: Element,
18+
hocDisplayNames: null | Array<string>,
19+
compiledWithForget: boolean,
2120
};
2221

23-
export default function InspectedElementBadges({element}: Props): React.Node {
24-
const {hocDisplayNames, compiledWithForget} = element;
22+
export default function InspectedElementBadges({
23+
hocDisplayNames,
24+
compiledWithForget,
25+
}: Props): React.Node {
26+
if (
27+
!compiledWithForget &&
28+
(hocDisplayNames == null || hocDisplayNames.length === 0)
29+
) {
30+
return null;
31+
}
2532

2633
return (
2734
<div className={styles.Root}>

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.css

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
line-height: var(--line-height-data);
2626
}
2727

28+
.InspectedElementBadgesContainer {
29+
padding: 0.25rem;
30+
}
31+
2832
.Owner {
2933
border-radius: 0.25rem;
3034
padding: 0.125rem 0.25rem;

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,12 @@ export default function InspectedElementView({
8585
return (
8686
<Fragment>
8787
<div className={styles.InspectedElement}>
88-
<InspectedElementBadges element={element} />
88+
<div className={styles.InspectedElementBadgesContainer}>
89+
<InspectedElementBadges
90+
hocDisplayNames={element.hocDisplayNames}
91+
compiledWithForget={element.compiledWithForget}
92+
/>
93+
</div>
8994

9095
<InspectedElementPropsTree
9196
bridge={bridge}

packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js

+12-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ import {
1717
TREE_OPERATION_UPDATE_TREE_BASE_DURATION,
1818
TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS,
1919
} from 'react-devtools-shared/src/constants';
20-
import {utfDecodeStringWithRanges} from 'react-devtools-shared/src/utils';
20+
import {
21+
parseElementDisplayNameFromBackend,
22+
utfDecodeStringWithRanges,
23+
} from 'react-devtools-shared/src/utils';
2124
import {ElementTypeRoot} from 'react-devtools-shared/src/frontend/types';
2225
import ProfilerStore from 'react-devtools-shared/src/devtools/ProfilerStore';
2326

@@ -133,6 +136,7 @@ function recursivelyInitializeTree(
133136
id,
134137
): any): number),
135138
type: node.type,
139+
compiledWithForget: node.compiledWithForget,
136140
});
137141

138142
node.children.forEach(childID =>
@@ -214,6 +218,7 @@ function updateTree(
214218
parentID: 0,
215219
treeBaseDuration: 0, // This will be updated by a subsequent operation
216220
type,
221+
compiledWithForget: false,
217222
};
218223

219224
nodes.set(id, node);
@@ -241,15 +246,19 @@ function updateTree(
241246
const parentNode = getClonedNode(parentID);
242247
parentNode.children = parentNode.children.concat(id);
243248

249+
const {formattedDisplayName, hocDisplayNames, compiledWithForget} =
250+
parseElementDisplayNameFromBackend(displayName, type);
251+
244252
const node: CommitTreeNode = {
245253
children: [],
246-
displayName,
247-
hocDisplayNames: null,
254+
displayName: formattedDisplayName,
255+
hocDisplayNames: hocDisplayNames,
248256
id,
249257
key,
250258
parentID,
251259
treeBaseDuration: 0, // This will be updated by a subsequent operation
252260
type,
261+
compiledWithForget,
253262
};
254263

255264
nodes.set(id, node);

packages/react-devtools-shared/src/devtools/views/Profiler/FlamegraphChartBuilder.js

+11-3
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,14 @@ export function getChartData({
7575
throw Error(`Could not find node with id "${id}" in commit tree`);
7676
}
7777

78-
const {children, displayName, hocDisplayNames, key, treeBaseDuration} =
79-
node;
78+
const {
79+
children,
80+
displayName,
81+
hocDisplayNames,
82+
key,
83+
treeBaseDuration,
84+
compiledWithForget,
85+
} = node;
8086

8187
const actualDuration = fiberActualDurations.get(id) || 0;
8288
const selfDuration = fiberSelfDurations.get(id) || 0;
@@ -86,11 +92,13 @@ export function getChartData({
8692
const maybeKey = key !== null ? ` key="${key}"` : '';
8793

8894
let maybeBadge = '';
95+
const maybeForgetBadge = compiledWithForget ? '✨ ' : '';
96+
8997
if (hocDisplayNames !== null && hocDisplayNames.length > 0) {
9098
maybeBadge = ` (${hocDisplayNames[0]})`;
9199
}
92100

93-
let label = `${name}${maybeBadge}${maybeKey}`;
101+
let label = `${maybeForgetBadge}${name}${maybeBadge}${maybeKey}`;
94102
if (didRender) {
95103
label += ` (${formatDuration(selfDuration)}ms of ${formatDuration(
96104
actualDuration,

packages/react-devtools-shared/src/devtools/views/Profiler/HoveredFiberInfo.css

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
.Toolbar {
22
padding: 0.25rem 0;
3-
margin-bottom: 0.25rem;
43
flex: 0 0 auto;
54
display: flex;
6-
align-items: center;
5+
flex-direction: column;
6+
gap: 0.25rem;
7+
}
8+
9+
.BadgesContainer {
10+
display: flex;
11+
flex-direction: column;
12+
gap: 0.25rem;
13+
}
14+
15+
.BadgesContainer:not(:empty) {
16+
padding-bottom: 0.25rem;
717
border-bottom: 1px solid var(--color-border);
818
}
919

@@ -20,10 +30,11 @@
2030
white-space: nowrap;
2131
overflow-x: hidden;
2232
text-overflow: ellipsis;
33+
padding-bottom: 0.25rem;
34+
border-bottom: 1px solid var(--color-border);
2335
}
2436

2537
.CurrentCommit {
26-
margin: 0.25rem 0;
2738
display: block;
2839
width: 100%;
2940
text-align: left;

packages/react-devtools-shared/src/devtools/views/Profiler/HoveredFiberInfo.js

+37-7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
import * as React from 'react';
1111
import {Fragment, useContext} from 'react';
12+
13+
import InspectedElementBadges from '../Components/InspectedElementBadges';
1214
import {ProfilerContext} from './ProfilerContext';
1315
import {formatDuration} from './utils';
1416
import WhatChanged from './WhatChanged';
@@ -34,11 +36,21 @@ export default function HoveredFiberInfo({fiberData}: Props): React.Node {
3436
const {id, name} = fiberData;
3537
const {profilingCache} = profilerStore;
3638

39+
if (rootID === null || selectedCommitIndex === null) {
40+
return null;
41+
}
42+
3743
const commitIndices = profilingCache.getFiberCommits({
38-
fiberID: ((id: any): number),
39-
rootID: ((rootID: any): number),
44+
fiberID: id,
45+
rootID,
4046
});
4147

48+
const {nodes} = profilingCache.getCommitTree({
49+
rootID,
50+
commitIndex: selectedCommitIndex,
51+
});
52+
const node = nodes.get(id);
53+
4254
let renderDurationInfo = null;
4355
let i = 0;
4456
for (i = 0; i < commitIndices.length; i++) {
@@ -51,7 +63,8 @@ export default function HoveredFiberInfo({fiberData}: Props): React.Node {
5163

5264
renderDurationInfo = (
5365
<div key={commitIndex} className={styles.CurrentCommit}>
54-
{formatDuration(selfDuration)}ms of {formatDuration(actualDuration)}ms
66+
<strong>Duration:</strong> {formatDuration(selfDuration)}ms of{' '}
67+
{formatDuration(actualDuration)}ms
5568
</div>
5669
);
5770

@@ -63,10 +76,27 @@ export default function HoveredFiberInfo({fiberData}: Props): React.Node {
6376
<Fragment>
6477
<div className={styles.Toolbar}>
6578
<div className={styles.Component}>{name}</div>
66-
</div>
67-
<div className={styles.Content}>
68-
{renderDurationInfo || <div>Did not render.</div>}
69-
<WhatChanged fiberID={((id: any): number)} />
79+
80+
{node != null && (
81+
<div className={styles.BadgesContainer}>
82+
<InspectedElementBadges
83+
hocDisplayNames={node.hocDisplayNames}
84+
compiledWithForget={node.compiledWithForget}
85+
/>
86+
87+
{node.compiledWithForget && (
88+
<div>
89+
✨ This component has been auto-memoized by the React Compiler.
90+
</div>
91+
)}
92+
</div>
93+
)}
94+
95+
<div className={styles.Content}>
96+
{renderDurationInfo || <div>Did not render.</div>}
97+
98+
<WhatChanged fiberID={id} />
99+
</div>
70100
</div>
71101
</Fragment>
72102
);

packages/react-devtools-shared/src/devtools/views/Profiler/RankedChartBuilder.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export function getChartData({
6161
throw Error(`Could not find node with id "${id}" in commit tree`);
6262
}
6363

64-
const {displayName, key, parentID, type} = node;
64+
const {displayName, key, parentID, type, compiledWithForget} = node;
6565

6666
// Don't show the root node in this chart.
6767
if (parentID === 0) {
@@ -72,6 +72,7 @@ export function getChartData({
7272

7373
const name = displayName || 'Anonymous';
7474
const maybeKey = key !== null ? ` key="${key}"` : '';
75+
const maybeForgetBadge = compiledWithForget ? '✨ ' : '';
7576

7677
let maybeBadge = '';
7778
if (type === ElementTypeForwardRef) {
@@ -80,7 +81,7 @@ export function getChartData({
8081
maybeBadge = ' (Memo)';
8182
}
8283

83-
const label = `${name}${maybeBadge}${maybeKey} (${formatDuration(
84+
const label = `${maybeForgetBadge}${name}${maybeBadge}${maybeKey} (${formatDuration(
8485
selfDuration,
8586
)}ms)`;
8687
chartNodes.push({

packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.css

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
padding: 0.5rem;
1212
user-select: none;
1313
overflow-y: auto;
14+
display: flex;
15+
flex-direction: column;
16+
gap: 0.5rem;
1417
}
1518

1619
.Component {

0 commit comments

Comments
 (0)