Track Owner for Server Components in DEV (#28753)

This implements the concept of a DEV-only "owner" for Server Components.
The owner concept isn't really super useful. We barely use it anymore,
but we do have it as a concept in DevTools in a couple of cases so this
adds it for parity. However, this is mainly interesting because it could
be used to wire up future owner-based stacks.

I do this by outlining the DebugInfo for a Server Component
(ReactComponentInfo). Then I just rely on Flight deduping to refer to
that. I refer to the same thing by referential equality so that we can
associate a Server Component parent in DebugInfo with an owner.

If you suspend and replay a Server Component, we have to restore the
same owner. To do that, I did a little ugly hack and stashed it on the
thenable state object. Felt unnecessarily complicated to add a stateful
wrapper for this one dev-only case.

The owner could really be anything since it could be coming from a
different implementation. Because this is the first time we have an
owner other than Fiber, I have to fix up a bunch of places that assumes
Fiber. I mainly did the `typeof owner.tag === 'number'` to assume it's a
Fiber for now.

This also doesn't actually add it to DevTools / RN Inspector yet. I just
ignore them there for now.

Because Server Components can be async the owner isn't tracked after an
await. We need per-component AsyncLocalStorage for that. This can be
done in a follow up.
This commit is contained in:
Sebastian Markbåge
2024-04-05 12:48:52 -04:00
committed by GitHub
parent e3ebcd54b9
commit f33a6b69c6
20 changed files with 291 additions and 158 deletions

View File

@@ -26,10 +26,7 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
const {ReactCurrentDispatcher} = ReactSharedInternals;
let prefix;
export function describeBuiltInComponentFrame(
name: string,
ownerFn: void | null | Function,
): string {
export function describeBuiltInComponentFrame(name: string): string {
if (enableComponentStackLocations) {
if (prefix === undefined) {
// Extract the VM specific prefix used by each line.
@@ -43,19 +40,12 @@ export function describeBuiltInComponentFrame(
// We use the prefix to ensure our stacks line up with native stack frames.
return '\n' + prefix + name;
} else {
let ownerName = null;
if (__DEV__ && ownerFn) {
ownerName = ownerFn.displayName || ownerFn.name || null;
}
return describeComponentFrame(name, ownerName);
return describeComponentFrame(name);
}
}
export function describeDebugInfoFrame(name: string, env: ?string): string {
return describeBuiltInComponentFrame(
name + (env ? ' (' + env + ')' : ''),
null,
);
return describeBuiltInComponentFrame(name + (env ? ' (' + env + ')' : ''));
}
let reentry = false;
@@ -298,29 +288,19 @@ export function describeNativeComponentFrame(
return syntheticFrame;
}
function describeComponentFrame(name: null | string, ownerName: null | string) {
let sourceInfo = '';
if (ownerName) {
sourceInfo = ' (created by ' + ownerName + ')';
}
return '\n in ' + (name || 'Unknown') + sourceInfo;
function describeComponentFrame(name: null | string) {
return '\n in ' + (name || 'Unknown');
}
export function describeClassComponentFrame(
ctor: Function,
ownerFn: void | null | Function,
): string {
export function describeClassComponentFrame(ctor: Function): string {
if (enableComponentStackLocations) {
return describeNativeComponentFrame(ctor, true);
} else {
return describeFunctionComponentFrame(ctor, ownerFn);
return describeFunctionComponentFrame(ctor);
}
}
export function describeFunctionComponentFrame(
fn: Function,
ownerFn: void | null | Function,
): string {
export function describeFunctionComponentFrame(fn: Function): string {
if (enableComponentStackLocations) {
return describeNativeComponentFrame(fn, false);
} else {
@@ -328,11 +308,7 @@ export function describeFunctionComponentFrame(
return '';
}
const name = fn.displayName || fn.name || null;
let ownerName = null;
if (__DEV__ && ownerFn) {
ownerName = ownerFn.displayName || ownerFn.name || null;
}
return describeComponentFrame(name, ownerName);
return describeComponentFrame(name);
}
}
@@ -341,10 +317,7 @@ function shouldConstruct(Component: Function) {
return !!(prototype && prototype.isReactComponent);
}
export function describeUnknownElementTypeFrameInDEV(
type: any,
ownerFn: void | null | Function,
): string {
export function describeUnknownElementTypeFrameInDEV(type: any): string {
if (!__DEV__) {
return '';
}
@@ -355,32 +328,32 @@ export function describeUnknownElementTypeFrameInDEV(
if (enableComponentStackLocations) {
return describeNativeComponentFrame(type, shouldConstruct(type));
} else {
return describeFunctionComponentFrame(type, ownerFn);
return describeFunctionComponentFrame(type);
}
}
if (typeof type === 'string') {
return describeBuiltInComponentFrame(type, ownerFn);
return describeBuiltInComponentFrame(type);
}
switch (type) {
case REACT_SUSPENSE_TYPE:
return describeBuiltInComponentFrame('Suspense', ownerFn);
return describeBuiltInComponentFrame('Suspense');
case REACT_SUSPENSE_LIST_TYPE:
return describeBuiltInComponentFrame('SuspenseList', ownerFn);
return describeBuiltInComponentFrame('SuspenseList');
}
if (typeof type === 'object') {
switch (type.$$typeof) {
case REACT_FORWARD_REF_TYPE:
return describeFunctionComponentFrame(type.render, ownerFn);
return describeFunctionComponentFrame(type.render);
case REACT_MEMO_TYPE:
// Memo may contain any component type so we recursively resolve it.
return describeUnknownElementTypeFrameInDEV(type.type, ownerFn);
return describeUnknownElementTypeFrameInDEV(type.type);
case REACT_LAZY_TYPE: {
const lazyComponent: LazyComponent<any, any> = (type: any);
const payload = lazyComponent._payload;
const init = lazyComponent._init;
try {
// Lazy may contain any component type so we recursively resolve it.
return describeUnknownElementTypeFrameInDEV(init(payload), ownerFn);
return describeUnknownElementTypeFrameInDEV(init(payload));
} catch (x) {}
}
}