mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
[DevTools] Add Badge to Owners and sometimes stack traces (#34106)
Stacked on #34101. This adds a badge to owners if they are different from the currently selected component's environment. <img width="590" height="566" alt="Screenshot 2025-08-04 at 5 15 02 PM" src="https://github.com/user-attachments/assets/e898254f-1b4c-498e-8713-978d90545340" /> We also add one to the end of stack traces if the stack trace has a different environment than the owner which can happen when you call a function (without rendering a component) into a third party environment but the owner component was in the first party. One awkward thing is that Suspense boundaries are always in the client environment so their Server Components are always badged.
This commit is contained in:
committed by
GitHub
parent
4c9c109cea
commit
738aebdbac
@@ -862,6 +862,7 @@ describe('ProfilingCache', () => {
|
||||
{
|
||||
"compiledWithForget": false,
|
||||
"displayName": "render()",
|
||||
"env": null,
|
||||
"hocDisplayNames": null,
|
||||
"id": 1,
|
||||
"key": null,
|
||||
@@ -903,6 +904,7 @@ describe('ProfilingCache', () => {
|
||||
{
|
||||
"compiledWithForget": false,
|
||||
"displayName": "createRoot()",
|
||||
"env": null,
|
||||
"hocDisplayNames": null,
|
||||
"id": 1,
|
||||
"key": null,
|
||||
@@ -943,6 +945,7 @@ describe('ProfilingCache', () => {
|
||||
{
|
||||
"compiledWithForget": false,
|
||||
"displayName": "createRoot()",
|
||||
"env": null,
|
||||
"hocDisplayNames": null,
|
||||
"id": 1,
|
||||
"key": null,
|
||||
|
||||
@@ -4818,6 +4818,7 @@ export function attach(
|
||||
displayName: getDisplayNameForFiber(fiber) || 'Anonymous',
|
||||
id: instance.id,
|
||||
key: fiber.key,
|
||||
env: null,
|
||||
type: getElementTypeForFiber(fiber),
|
||||
};
|
||||
} else {
|
||||
@@ -4826,6 +4827,7 @@ export function attach(
|
||||
displayName: componentInfo.name || 'Anonymous',
|
||||
id: instance.id,
|
||||
key: componentInfo.key == null ? null : componentInfo.key,
|
||||
env: componentInfo.env == null ? null : componentInfo.env,
|
||||
type: ElementTypeVirtual,
|
||||
};
|
||||
}
|
||||
@@ -5451,6 +5453,8 @@ export function attach(
|
||||
// List of owners
|
||||
owners,
|
||||
|
||||
env: null,
|
||||
|
||||
rootType,
|
||||
rendererPackageName: renderer.rendererPackageName,
|
||||
rendererVersion: renderer.version,
|
||||
@@ -5554,6 +5558,8 @@ export function attach(
|
||||
// List of owners
|
||||
owners,
|
||||
|
||||
env: componentInfo.env == null ? null : componentInfo.env,
|
||||
|
||||
rootType,
|
||||
rendererPackageName: renderer.rendererPackageName,
|
||||
rendererVersion: renderer.version,
|
||||
|
||||
@@ -795,6 +795,7 @@ export function attach(
|
||||
displayName: getData(owner).displayName || 'Unknown',
|
||||
id: getID(owner),
|
||||
key: element.key,
|
||||
env: null,
|
||||
type: getElementType(owner),
|
||||
});
|
||||
if (owner._currentElement) {
|
||||
@@ -857,6 +858,8 @@ export function attach(
|
||||
// List of owners
|
||||
owners,
|
||||
|
||||
env: null,
|
||||
|
||||
rootType: null,
|
||||
rendererPackageName: null,
|
||||
rendererVersion: null,
|
||||
|
||||
@@ -256,6 +256,7 @@ export type SerializedElement = {
|
||||
displayName: string | null,
|
||||
id: number,
|
||||
key: number | string | null,
|
||||
env: null | string,
|
||||
type: ElementType,
|
||||
};
|
||||
|
||||
@@ -301,6 +302,10 @@ export type InspectedElement = {
|
||||
|
||||
// List of owners
|
||||
owners: Array<SerializedElement> | null,
|
||||
|
||||
// Environment name that this component executed in or null for the client
|
||||
env: string | null,
|
||||
|
||||
source: ReactFunctionLocation | null,
|
||||
|
||||
type: ElementType,
|
||||
|
||||
@@ -255,6 +255,7 @@ export function convertInspectedElementBackendToFrontend(
|
||||
id,
|
||||
type,
|
||||
owners,
|
||||
env,
|
||||
source,
|
||||
context,
|
||||
hooks,
|
||||
@@ -299,6 +300,7 @@ export function convertInspectedElementBackendToFrontend(
|
||||
owners === null
|
||||
? null
|
||||
: owners.map(backendToFrontendSerializedElementMapper),
|
||||
env,
|
||||
context: hydrateHelper(context),
|
||||
hooks: hydrateHelper(hooks),
|
||||
props: hydrateHelper(props),
|
||||
|
||||
@@ -16,18 +16,21 @@ import styles from './ElementBadges.css';
|
||||
|
||||
type Props = {
|
||||
hocDisplayNames: Array<string> | null,
|
||||
environmentName: string | null,
|
||||
compiledWithForget: boolean,
|
||||
className?: string,
|
||||
};
|
||||
|
||||
export default function ElementBadges({
|
||||
compiledWithForget,
|
||||
environmentName,
|
||||
hocDisplayNames,
|
||||
className = '',
|
||||
}: Props): React.Node {
|
||||
if (
|
||||
!compiledWithForget &&
|
||||
(hocDisplayNames == null || hocDisplayNames.length === 0)
|
||||
(hocDisplayNames == null || hocDisplayNames.length === 0) &&
|
||||
environmentName == null
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
@@ -36,6 +39,8 @@ export default function ElementBadges({
|
||||
<div className={`${styles.Root} ${className}`}>
|
||||
{compiledWithForget && <ForgetBadge indexable={false} />}
|
||||
|
||||
{environmentName != null ? <Badge>{environmentName}</Badge> : null}
|
||||
|
||||
{hocDisplayNames != null && hocDisplayNames.length > 0 && (
|
||||
<Badge>{hocDisplayNames[0]}</Badge>
|
||||
)}
|
||||
|
||||
@@ -150,13 +150,28 @@ function SuspendedByRow({
|
||||
</Button>
|
||||
{isOpen && (
|
||||
<div className={styles.CollapsableContent}>
|
||||
{showIOStack && <StackTraceView stack={ioInfo.stack} />}
|
||||
{showIOStack && (
|
||||
<StackTraceView
|
||||
stack={ioInfo.stack}
|
||||
environmentName={
|
||||
ioOwner !== null && ioOwner.env === ioInfo.env
|
||||
? null
|
||||
: ioInfo.env
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{(showIOStack || !showAwaitStack) &&
|
||||
ioOwner !== null &&
|
||||
ioOwner.id !== inspectedElement.id ? (
|
||||
<OwnerView
|
||||
key={ioOwner.id}
|
||||
displayName={ioOwner.displayName || 'Anonymous'}
|
||||
environmentName={
|
||||
ioOwner.env === inspectedElement.env &&
|
||||
ioOwner.env === ioInfo.env
|
||||
? null
|
||||
: ioOwner.env
|
||||
}
|
||||
hocDisplayNames={ioOwner.hocDisplayNames}
|
||||
compiledWithForget={ioOwner.compiledWithForget}
|
||||
id={ioOwner.id}
|
||||
@@ -168,12 +183,25 @@ function SuspendedByRow({
|
||||
<>
|
||||
<div className={styles.SmallHeader}>awaited at:</div>
|
||||
{asyncInfo.stack !== null && asyncInfo.stack.length > 0 && (
|
||||
<StackTraceView stack={asyncInfo.stack} />
|
||||
<StackTraceView
|
||||
stack={asyncInfo.stack}
|
||||
environmentName={
|
||||
asyncOwner !== null && asyncOwner.env === asyncInfo.env
|
||||
? null
|
||||
: asyncInfo.env
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{asyncOwner !== null && asyncOwner.id !== inspectedElement.id ? (
|
||||
<OwnerView
|
||||
key={asyncOwner.id}
|
||||
displayName={asyncOwner.displayName || 'Anonymous'}
|
||||
environmentName={
|
||||
asyncOwner.env === inspectedElement.env &&
|
||||
asyncOwner.env === asyncInfo.env
|
||||
? null
|
||||
: asyncOwner.env
|
||||
}
|
||||
hocDisplayNames={asyncOwner.hocDisplayNames}
|
||||
compiledWithForget={asyncOwner.compiledWithForget}
|
||||
id={asyncOwner.id}
|
||||
|
||||
@@ -174,6 +174,9 @@ export default function InspectedElementView({
|
||||
key={owner.id}
|
||||
displayName={owner.displayName || 'Anonymous'}
|
||||
hocDisplayNames={owner.hocDisplayNames}
|
||||
environmentName={
|
||||
inspectedElement.env === owner.env ? null : owner.env
|
||||
}
|
||||
compiledWithForget={owner.compiledWithForget}
|
||||
id={owner.id}
|
||||
isInStore={store.containsElement(owner.id)}
|
||||
|
||||
@@ -20,6 +20,7 @@ import styles from './OwnerView.css';
|
||||
type OwnerViewProps = {
|
||||
displayName: string,
|
||||
hocDisplayNames: Array<string> | null,
|
||||
environmentName: string | null,
|
||||
compiledWithForget: boolean,
|
||||
id: number,
|
||||
isInStore: boolean,
|
||||
@@ -27,6 +28,7 @@ type OwnerViewProps = {
|
||||
|
||||
export default function OwnerView({
|
||||
displayName,
|
||||
environmentName,
|
||||
hocDisplayNames,
|
||||
compiledWithForget,
|
||||
id,
|
||||
@@ -65,6 +67,7 @@ export default function OwnerView({
|
||||
<ElementBadges
|
||||
hocDisplayNames={hocDisplayNames}
|
||||
compiledWithForget={compiledWithForget}
|
||||
environmentName={environmentName}
|
||||
/>
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
@@ -220,6 +220,7 @@ function ElementsDropdown({owners, selectOwner}: ElementsDropdownProps) {
|
||||
|
||||
<ElementBadges
|
||||
hocDisplayNames={owner.hocDisplayNames}
|
||||
environmentName={owner.env}
|
||||
compiledWithForget={owner.compiledWithForget}
|
||||
className={styles.BadgesBlock}
|
||||
/>
|
||||
@@ -268,6 +269,7 @@ function ElementView({isSelected, owner, selectOwner}: ElementViewProps) {
|
||||
|
||||
<ElementBadges
|
||||
hocDisplayNames={hocDisplayNames}
|
||||
environmentName={owner.env}
|
||||
compiledWithForget={compiledWithForget}
|
||||
className={styles.BadgesBlock}
|
||||
/>
|
||||
|
||||
@@ -12,6 +12,8 @@ import {use, useContext} from 'react';
|
||||
|
||||
import useOpenResource from '../useOpenResource';
|
||||
|
||||
import ElementBadges from './ElementBadges';
|
||||
|
||||
import styles from './StackTraceView.css';
|
||||
|
||||
import type {
|
||||
@@ -28,9 +30,13 @@ import formatLocationForDisplay from './formatLocationForDisplay';
|
||||
|
||||
type CallSiteViewProps = {
|
||||
callSite: ReactCallSite,
|
||||
environmentName: null | string,
|
||||
};
|
||||
|
||||
export function CallSiteView({callSite}: CallSiteViewProps): React.Node {
|
||||
export function CallSiteView({
|
||||
callSite,
|
||||
environmentName,
|
||||
}: CallSiteViewProps): React.Node {
|
||||
const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
|
||||
|
||||
const [virtualFunctionName, virtualURL, virtualLine, virtualColumn] =
|
||||
@@ -64,19 +70,33 @@ export function CallSiteView({callSite}: CallSiteViewProps): React.Node {
|
||||
title={url + ':' + line}>
|
||||
{formatLocationForDisplay(url, line, column)}
|
||||
</span>
|
||||
<ElementBadges environmentName={environmentName} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type Props = {
|
||||
stack: ReactStackTrace,
|
||||
environmentName: null | string,
|
||||
};
|
||||
|
||||
export default function StackTraceView({stack}: Props): React.Node {
|
||||
export default function StackTraceView({
|
||||
stack,
|
||||
environmentName,
|
||||
}: Props): React.Node {
|
||||
return (
|
||||
<div className={styles.StackTraceView}>
|
||||
{stack.map((callSite, index) => (
|
||||
<CallSiteView key={index} callSite={callSite} />
|
||||
<CallSiteView
|
||||
key={index}
|
||||
callSite={callSite}
|
||||
environmentName={
|
||||
// Badge last row
|
||||
// TODO: If we start ignore listing the last row, we should badge the last
|
||||
// non-ignored row.
|
||||
index === stack.length - 1 ? environmentName : null
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -208,6 +208,7 @@ export type SerializedElement = {
|
||||
displayName: string | null,
|
||||
id: number,
|
||||
key: number | string | null,
|
||||
env: null | string,
|
||||
hocDisplayNames: Array<string> | null,
|
||||
compiledWithForget: boolean,
|
||||
type: ElementType,
|
||||
@@ -265,6 +266,9 @@ export type InspectedElement = {
|
||||
// List of owners
|
||||
owners: Array<SerializedElement> | null,
|
||||
|
||||
// Environment name that this component executed in or null for the client
|
||||
env: string | null,
|
||||
|
||||
// Location of component in source code.
|
||||
source: ReactFunctionLocation | null,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user