mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 00:20:04 +01:00
[DevTools] Show Owner Stacks in "rendered by" View (#34130)
This shows the stack trace of the JSX at each level so now you can also jump to the code location for the JSX callsite. The visual is similar to the owner stacks with `createTask` except when you click the `<...>` you jump to the Instance in the Components panel. <img width="593" height="450" alt="Screenshot 2025-08-08 at 12 19 21 AM" src="https://github.com/user-attachments/assets/dac35faf-9d99-46ce-8b41-7c6fe24625d2" /> I'm not sure it's really necessary to have all the JSX stacks of every owner. We could just have it for the current component and then the rest of the owners you could get to if you just click that owner instance. As a bonus, I also use the JSX callsite as the fallback for the "View Source" button. This is primarily useful for built-ins like `<div>` and `<Suspense>` that don't have any implementation to jump to anyway. It's useful to be able to jump to where a boundary was defined.
This commit is contained in:
parent
59ef3c4baf
commit
7a934a16b8
|
|
@ -64,11 +64,22 @@ async function selectElement(
|
|||
createTestNameSelector('InspectedElementView-Owners'),
|
||||
])[0];
|
||||
|
||||
if (!ownersList) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const owners = findAllNodes(ownersList, [
|
||||
createTestNameSelector('OwnerView'),
|
||||
]);
|
||||
|
||||
return (
|
||||
title &&
|
||||
title.innerText.includes(titleText) &&
|
||||
ownersList &&
|
||||
ownersList.innerText.includes(ownersListText)
|
||||
owners &&
|
||||
owners
|
||||
.map(node => node.innerText)
|
||||
.join('\n')
|
||||
.includes(ownersListText)
|
||||
);
|
||||
},
|
||||
{titleText: displayName, ownersListText: waitForOwnersText}
|
||||
|
|
|
|||
|
|
@ -949,6 +949,7 @@ describe('ProfilingCache', () => {
|
|||
"hocDisplayNames": null,
|
||||
"id": 1,
|
||||
"key": null,
|
||||
"stack": null,
|
||||
"type": 11,
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -4991,6 +4991,10 @@ export function attach(
|
|||
id: instance.id,
|
||||
key: fiber.key,
|
||||
env: null,
|
||||
stack:
|
||||
fiber._debugOwner == null || fiber._debugStack == null
|
||||
? null
|
||||
: parseStackTrace(fiber._debugStack, 1),
|
||||
type: getElementTypeForFiber(fiber),
|
||||
};
|
||||
} else {
|
||||
|
|
@ -5000,6 +5004,10 @@ export function attach(
|
|||
id: instance.id,
|
||||
key: componentInfo.key == null ? null : componentInfo.key,
|
||||
env: componentInfo.env == null ? null : componentInfo.env,
|
||||
stack:
|
||||
componentInfo.owner == null || componentInfo.debugStack == null
|
||||
? null
|
||||
: parseStackTrace(componentInfo.debugStack, 1),
|
||||
type: ElementTypeVirtual,
|
||||
};
|
||||
}
|
||||
|
|
@ -5598,6 +5606,11 @@ export function attach(
|
|||
|
||||
source,
|
||||
|
||||
stack:
|
||||
fiber._debugOwner == null || fiber._debugStack == null
|
||||
? null
|
||||
: parseStackTrace(fiber._debugStack, 1),
|
||||
|
||||
// Does the component have legacy context attached to it.
|
||||
hasLegacyContext,
|
||||
|
||||
|
|
@ -5698,6 +5711,11 @@ export function attach(
|
|||
|
||||
source,
|
||||
|
||||
stack:
|
||||
componentInfo.owner == null || componentInfo.debugStack == null
|
||||
? null
|
||||
: parseStackTrace(componentInfo.debugStack, 1),
|
||||
|
||||
// Does the component have legacy context attached to it.
|
||||
hasLegacyContext: false,
|
||||
|
||||
|
|
|
|||
|
|
@ -796,6 +796,7 @@ export function attach(
|
|||
id: getID(owner),
|
||||
key: element.key,
|
||||
env: null,
|
||||
stack: null,
|
||||
type: getElementType(owner),
|
||||
});
|
||||
if (owner._currentElement) {
|
||||
|
|
@ -837,6 +838,8 @@ export function attach(
|
|||
|
||||
source: null,
|
||||
|
||||
stack: null,
|
||||
|
||||
// Only legacy context exists in legacy versions.
|
||||
hasLegacyContext: true,
|
||||
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ export type SerializedElement = {
|
|||
id: number,
|
||||
key: number | string | null,
|
||||
env: null | string,
|
||||
stack: null | ReactStackTrace,
|
||||
type: ElementType,
|
||||
};
|
||||
|
||||
|
|
@ -308,6 +309,9 @@ export type InspectedElement = {
|
|||
|
||||
source: ReactFunctionLocation | null,
|
||||
|
||||
// The location of the JSX creation.
|
||||
stack: ReactStackTrace | null,
|
||||
|
||||
type: ElementType,
|
||||
|
||||
// Meta information about the root this element belongs to.
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ export function convertInspectedElementBackendToFrontend(
|
|||
owners,
|
||||
env,
|
||||
source,
|
||||
stack,
|
||||
context,
|
||||
hooks,
|
||||
plugins,
|
||||
|
|
@ -295,6 +296,7 @@ export function convertInspectedElementBackendToFrontend(
|
|||
// Previous backend implementations (<= 6.1.5) have a different interface for Source.
|
||||
// This gates the source features for only compatible backends: >= 6.1.6
|
||||
source: Array.isArray(source) ? source : null,
|
||||
stack: stack,
|
||||
type,
|
||||
owners:
|
||||
owners === null
|
||||
|
|
|
|||
|
|
@ -51,12 +51,19 @@ export default function InspectedElementWrapper(_: Props): React.Node {
|
|||
|
||||
const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
|
||||
|
||||
const source =
|
||||
inspectedElement == null
|
||||
? null
|
||||
: inspectedElement.source != null
|
||||
? inspectedElement.source
|
||||
: inspectedElement.stack != null && inspectedElement.stack.length > 0
|
||||
? inspectedElement.stack[0]
|
||||
: null;
|
||||
|
||||
const symbolicatedSourcePromise: null | Promise<ReactFunctionLocation | null> =
|
||||
React.useMemo(() => {
|
||||
if (inspectedElement == null) return null;
|
||||
if (fetchFileWithCaching == null) return Promise.resolve(null);
|
||||
|
||||
const {source} = inspectedElement;
|
||||
if (source == null) return Promise.resolve(null);
|
||||
|
||||
const [, sourceURL, line, column] = source;
|
||||
|
|
@ -66,7 +73,7 @@ export default function InspectedElementWrapper(_: Props): React.Node {
|
|||
line,
|
||||
column,
|
||||
);
|
||||
}, [inspectedElement]);
|
||||
}, [source]);
|
||||
|
||||
const element =
|
||||
inspectedElementID !== null
|
||||
|
|
@ -223,13 +230,12 @@ export default function InspectedElementWrapper(_: Props): React.Node {
|
|||
|
||||
{!alwaysOpenInEditor &&
|
||||
!!editorURL &&
|
||||
inspectedElement != null &&
|
||||
inspectedElement.source != null &&
|
||||
source != null &&
|
||||
symbolicatedSourcePromise != null && (
|
||||
<React.Suspense fallback={<Skeleton height={16} width={24} />}>
|
||||
<OpenInEditorButton
|
||||
editorURL={editorURL}
|
||||
source={inspectedElement.source}
|
||||
source={source}
|
||||
symbolicatedSourcePromise={symbolicatedSourcePromise}
|
||||
/>
|
||||
</React.Suspense>
|
||||
|
|
@ -276,7 +282,7 @@ export default function InspectedElementWrapper(_: Props): React.Node {
|
|||
|
||||
{!hideViewSourceAction && (
|
||||
<InspectedElementViewSourceButton
|
||||
source={inspectedElement ? inspectedElement.source : null}
|
||||
source={source}
|
||||
symbolicatedSourcePromise={symbolicatedSourcePromise}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import InspectedElementSuspendedBy from './InspectedElementSuspendedBy';
|
|||
import NativeStyleEditor from './NativeStyleEditor';
|
||||
import {enableStyleXFeatures} from 'react-devtools-feature-flags';
|
||||
import InspectedElementSourcePanel from './InspectedElementSourcePanel';
|
||||
import StackTraceView from './StackTraceView';
|
||||
import OwnerView from './OwnerView';
|
||||
|
||||
import styles from './InspectedElementView.css';
|
||||
|
|
@ -52,6 +53,7 @@ export default function InspectedElementView({
|
|||
symbolicatedSourcePromise,
|
||||
}: Props): React.Node {
|
||||
const {
|
||||
stack,
|
||||
owners,
|
||||
rendererPackageName,
|
||||
rendererVersion,
|
||||
|
|
@ -68,8 +70,9 @@ export default function InspectedElementView({
|
|||
? `${rendererPackageName}@${rendererVersion}`
|
||||
: null;
|
||||
const showOwnersList = owners !== null && owners.length > 0;
|
||||
const showStack = stack != null && stack.length > 0;
|
||||
const showRenderedBy =
|
||||
showOwnersList || rendererLabel !== null || rootType !== null;
|
||||
showStack || showOwnersList || rendererLabel !== null || rootType !== null;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
|
@ -168,20 +171,26 @@ export default function InspectedElementView({
|
|||
data-testname="InspectedElementView-Owners">
|
||||
<div className={styles.OwnersHeader}>rendered by</div>
|
||||
|
||||
{showStack ? <StackTraceView stack={stack} /> : null}
|
||||
{showOwnersList &&
|
||||
owners?.map(owner => (
|
||||
<OwnerView
|
||||
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)}
|
||||
type={owner.type}
|
||||
/>
|
||||
<>
|
||||
<OwnerView
|
||||
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)}
|
||||
type={owner.type}
|
||||
/>
|
||||
{owner.stack != null && owner.stack.length > 0 ? (
|
||||
<StackTraceView stack={owner.stack} />
|
||||
) : null}
|
||||
</>
|
||||
))}
|
||||
|
||||
{rootType !== null && (
|
||||
|
|
|
|||
|
|
@ -60,7 +60,8 @@ export default function OwnerView({
|
|||
<span className={styles.OwnerContent}>
|
||||
<span
|
||||
className={`${styles.Owner} ${isInStore ? '' : styles.NotInStore}`}
|
||||
title={displayName}>
|
||||
title={displayName}
|
||||
data-testname="OwnerView">
|
||||
{'<' + displayName + '>'}
|
||||
</span>
|
||||
|
||||
|
|
|
|||
|
|
@ -216,6 +216,7 @@ export type SerializedElement = {
|
|||
id: number,
|
||||
key: number | string | null,
|
||||
env: null | string,
|
||||
stack: null | ReactStackTrace,
|
||||
hocDisplayNames: Array<string> | null,
|
||||
compiledWithForget: boolean,
|
||||
type: ElementType,
|
||||
|
|
@ -279,6 +280,9 @@ export type InspectedElement = {
|
|||
// Location of component in source code.
|
||||
source: ReactFunctionLocation | null,
|
||||
|
||||
// The location of the JSX creation.
|
||||
stack: ReactStackTrace | null,
|
||||
|
||||
type: ElementType,
|
||||
|
||||
// Meta information about the root this element belongs to.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user