Skip to content

Commit

Permalink
feat[react-devtools]: display forget badge for components in profilin…
Browse files Browse the repository at this point in the history
…g 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">
  • Loading branch information
hoxyq committed May 7, 2024
1 parent c32ff0f commit e7d213d
Show file tree
Hide file tree
Showing 16 changed files with 170 additions and 68 deletions.
Expand Up @@ -124,8 +124,8 @@ describe('profiling charts', () => {
"actualDuration": 0,
"didRender": true,
"id": 5,
"label": "Memo(Child) key="third" (<0.1ms of <0.1ms)",
"name": "Memo(Child)",
"label": "Child (Memo) key="third" (<0.1ms of <0.1ms)",
"name": "Child",
"offset": 15,
"selfDuration": 0,
"treeBaseDuration": 0,
Expand All @@ -134,8 +134,8 @@ describe('profiling charts', () => {
"actualDuration": 2,
"didRender": true,
"id": 4,
"label": "Memo(Child) key="second" (2ms of 2ms)",
"name": "Memo(Child)",
"label": "Child (Memo) key="second" (2ms of 2ms)",
"name": "Child",
"offset": 13,
"selfDuration": 2,
"treeBaseDuration": 2,
Expand All @@ -144,8 +144,8 @@ describe('profiling charts', () => {
"actualDuration": 3,
"didRender": true,
"id": 3,
"label": "Memo(Child) key="first" (3ms of 3ms)",
"name": "Memo(Child)",
"label": "Child (Memo) key="first" (3ms of 3ms)",
"name": "Child",
"offset": 10,
"selfDuration": 3,
"treeBaseDuration": 3,
Expand Down Expand Up @@ -175,8 +175,8 @@ describe('profiling charts', () => {
"actualDuration": 0,
"didRender": false,
"id": 5,
"label": "Memo(Child) key="third"",
"name": "Memo(Child)",
"label": "Child (Memo) key="third"",
"name": "Child",
"offset": 15,
"selfDuration": 0,
"treeBaseDuration": 0,
Expand All @@ -185,8 +185,8 @@ describe('profiling charts', () => {
"actualDuration": 0,
"didRender": false,
"id": 4,
"label": "Memo(Child) key="second"",
"name": "Memo(Child)",
"label": "Child (Memo) key="second"",
"name": "Child",
"offset": 13,
"selfDuration": 0,
"treeBaseDuration": 2,
Expand All @@ -195,8 +195,8 @@ describe('profiling charts', () => {
"actualDuration": 0,
"didRender": false,
"id": 3,
"label": "Memo(Child) key="first"",
"name": "Memo(Child)",
"label": "Child (Memo) key="first"",
"name": "Child",
"offset": 10,
"selfDuration": 0,
"treeBaseDuration": 3,
Expand Down Expand Up @@ -264,20 +264,20 @@ describe('profiling charts', () => {
},
{
"id": 3,
"label": "Memo(Child) (Memo) key="first" (3ms)",
"name": "Memo(Child)",
"label": "Child (Memo) key="first" (3ms)",
"name": "Child",
"value": 3,
},
{
"id": 4,
"label": "Memo(Child) (Memo) key="second" (2ms)",
"name": "Memo(Child)",
"label": "Child (Memo) key="second" (2ms)",
"name": "Child",
"value": 2,
},
{
"id": 5,
"label": "Memo(Child) (Memo) key="third" (<0.1ms)",
"name": "Memo(Child)",
"label": "Child (Memo) key="third" (<0.1ms)",
"name": "Child",
"value": 0,
},
]
Expand Down
Expand Up @@ -214,6 +214,7 @@ export default class ProfilerStore extends EventEmitter<{
hocDisplayNames: element.hocDisplayNames,
key: element.key,
type: element.type,
compiledWithForget: element.compiledWithForget,
};
profilingSnapshots.set(elementID, snapshotNode);

Expand Down
@@ -1,5 +1,4 @@
.Root {
padding: 0.25rem;
user-select: none;
display: inline-flex;
}
Expand Down
Expand Up @@ -7,8 +7,6 @@
* @flow
*/

import type {Element} from 'react-devtools-shared/src/frontend/types';

import * as React from 'react';

import Badge from './Badge';
Expand All @@ -17,11 +15,20 @@ import ForgetBadge from './ForgetBadge';
import styles from './InspectedElementBadges.css';

type Props = {
element: Element,
hocDisplayNames: null | Array<string>,
compiledWithForget: boolean,
};

export default function InspectedElementBadges({element}: Props): React.Node {
const {hocDisplayNames, compiledWithForget} = element;
export default function InspectedElementBadges({
hocDisplayNames,
compiledWithForget,
}: Props): React.Node {
if (
!compiledWithForget &&
(hocDisplayNames == null || hocDisplayNames.length === 0)
) {
return null;
}

return (
<div className={styles.Root}>
Expand Down
Expand Up @@ -25,6 +25,10 @@
line-height: var(--line-height-data);
}

.InspectedElementBadgesContainer {
padding: 0.25rem;
}

.Owner {
border-radius: 0.25rem;
padding: 0.125rem 0.25rem;
Expand Down
Expand Up @@ -85,7 +85,12 @@ export default function InspectedElementView({
return (
<Fragment>
<div className={styles.InspectedElement}>
<InspectedElementBadges element={element} />
<div className={styles.InspectedElementBadgesContainer}>
<InspectedElementBadges
hocDisplayNames={element.hocDisplayNames}
compiledWithForget={element.compiledWithForget}
/>
</div>

<InspectedElementPropsTree
bridge={bridge}
Expand Down
Expand Up @@ -17,7 +17,10 @@ import {
TREE_OPERATION_UPDATE_TREE_BASE_DURATION,
TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS,
} from 'react-devtools-shared/src/constants';
import {utfDecodeStringWithRanges} from 'react-devtools-shared/src/utils';
import {
parseElementDisplayNameFromBackend,
utfDecodeStringWithRanges,
} from 'react-devtools-shared/src/utils';
import {ElementTypeRoot} from 'react-devtools-shared/src/frontend/types';
import ProfilerStore from 'react-devtools-shared/src/devtools/ProfilerStore';

Expand Down Expand Up @@ -133,6 +136,7 @@ function recursivelyInitializeTree(
id,
): any): number),
type: node.type,
compiledWithForget: node.compiledWithForget,
});

node.children.forEach(childID =>
Expand Down Expand Up @@ -214,6 +218,7 @@ function updateTree(
parentID: 0,
treeBaseDuration: 0, // This will be updated by a subsequent operation
type,
compiledWithForget: false,
};

nodes.set(id, node);
Expand Down Expand Up @@ -241,15 +246,19 @@ function updateTree(
const parentNode = getClonedNode(parentID);
parentNode.children = parentNode.children.concat(id);

const {formattedDisplayName, hocDisplayNames, compiledWithForget} =
parseElementDisplayNameFromBackend(displayName, type);

const node: CommitTreeNode = {
children: [],
displayName,
hocDisplayNames: null,
displayName: formattedDisplayName,
hocDisplayNames: hocDisplayNames,
id,
key,
parentID,
treeBaseDuration: 0, // This will be updated by a subsequent operation
type,
compiledWithForget,
};

nodes.set(id, node);
Expand Down
Expand Up @@ -75,8 +75,14 @@ export function getChartData({
throw Error(`Could not find node with id "${id}" in commit tree`);
}

const {children, displayName, hocDisplayNames, key, treeBaseDuration} =
node;
const {
children,
displayName,
hocDisplayNames,
key,
treeBaseDuration,
compiledWithForget,
} = node;

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

let maybeBadge = '';
const maybeForgetBadge = compiledWithForget ? '✨ ' : '';

if (hocDisplayNames !== null && hocDisplayNames.length > 0) {
maybeBadge = ` (${hocDisplayNames[0]})`;
}

let label = `${name}${maybeBadge}${maybeKey}`;
let label = `${maybeForgetBadge}${name}${maybeBadge}${maybeKey}`;
if (didRender) {
label += ` (${formatDuration(selfDuration)}ms of ${formatDuration(
actualDuration,
Expand Down
@@ -1,9 +1,19 @@
.Toolbar {
padding: 0.25rem 0;
margin-bottom: 0.25rem;
flex: 0 0 auto;
display: flex;
align-items: center;
flex-direction: column;
gap: 0.25rem;
}

.BadgesContainer {
display: flex;
flex-direction: column;
gap: 0.25rem;
}

.BadgesContainer:not(:empty) {
padding-bottom: 0.25rem;
border-bottom: 1px solid var(--color-border);
}

Expand All @@ -20,10 +30,11 @@
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
padding-bottom: 0.25rem;
border-bottom: 1px solid var(--color-border);
}

.CurrentCommit {
margin: 0.25rem 0;
display: block;
width: 100%;
text-align: left;
Expand Down
Expand Up @@ -9,6 +9,8 @@

import * as React from 'react';
import {Fragment, useContext} from 'react';

import InspectedElementBadges from '../Components/InspectedElementBadges';
import {ProfilerContext} from './ProfilerContext';
import {formatDuration} from './utils';
import WhatChanged from './WhatChanged';
Expand All @@ -34,11 +36,21 @@ export default function HoveredFiberInfo({fiberData}: Props): React.Node {
const {id, name} = fiberData;
const {profilingCache} = profilerStore;

if (rootID === null || selectedCommitIndex === null) {
return null;
}

const commitIndices = profilingCache.getFiberCommits({
fiberID: ((id: any): number),
rootID: ((rootID: any): number),
fiberID: id,
rootID,
});

const {nodes} = profilingCache.getCommitTree({
rootID,
commitIndex: selectedCommitIndex,
});
const node = nodes.get(id);

let renderDurationInfo = null;
let i = 0;
for (i = 0; i < commitIndices.length; i++) {
Expand All @@ -51,7 +63,8 @@ export default function HoveredFiberInfo({fiberData}: Props): React.Node {

renderDurationInfo = (
<div key={commitIndex} className={styles.CurrentCommit}>
{formatDuration(selfDuration)}ms of {formatDuration(actualDuration)}ms
<strong>Duration:</strong> {formatDuration(selfDuration)}ms of{' '}
{formatDuration(actualDuration)}ms
</div>
);

Expand All @@ -63,10 +76,27 @@ export default function HoveredFiberInfo({fiberData}: Props): React.Node {
<Fragment>
<div className={styles.Toolbar}>
<div className={styles.Component}>{name}</div>
</div>
<div className={styles.Content}>
{renderDurationInfo || <div>Did not render.</div>}
<WhatChanged fiberID={((id: any): number)} />

{node != null && (
<div className={styles.BadgesContainer}>
<InspectedElementBadges
hocDisplayNames={node.hocDisplayNames}
compiledWithForget={node.compiledWithForget}
/>

{node.compiledWithForget && (
<div>
✨ This component has been auto-memoized by the React Compiler.
</div>
)}
</div>
)}

<div className={styles.Content}>
{renderDurationInfo || <div>Did not render.</div>}

<WhatChanged fiberID={id} />
</div>
</div>
</Fragment>
);
Expand Down
Expand Up @@ -61,7 +61,7 @@ export function getChartData({
throw Error(`Could not find node with id "${id}" in commit tree`);
}

const {displayName, key, parentID, type} = node;
const {displayName, key, parentID, type, compiledWithForget} = node;

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

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

let maybeBadge = '';
if (type === ElementTypeForwardRef) {
Expand All @@ -80,7 +81,7 @@ export function getChartData({
maybeBadge = ' (Memo)';
}

const label = `${name}${maybeBadge}${maybeKey} (${formatDuration(
const label = `${maybeForgetBadge}${name}${maybeBadge}${maybeKey} (${formatDuration(
selfDuration,
)}ms)`;
chartNodes.push({
Expand Down
Expand Up @@ -11,6 +11,9 @@
padding: 0.5rem;
user-select: none;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 0.5rem;
}

.Component {
Expand Down

0 comments on commit e7d213d

Please sign in to comment.