mirror of
https://github.com/facebook/react.git
synced 2026-02-24 12:43:00 +00:00
This is the first step to experimenting with a new type of stack traces behind the `enableOwnerStacks` flag - in DEV only. The idea is to generate stacks that are more like if the JSX was a direct call even though it's actually a lazy call. Not only can you see which exact JSX call line number generated the erroring component but if that's inside an abstraction function, which function called that function and if it's a component, which component generated that component. For this to make sense it really need to be the "owner" stack rather than the parent stack like we do for other component stacks. On one hand it has more precise information but on the other hand it also loses context. For most types of problems the owner stack is the most useful though since it tells you which component rendered this component. The problem with the platform in its current state is that there's two ways to deal with stacks: 1) `new Error().stack` 2) `console.createTask()` The nice thing about `new Error().stack` is that we can extract the frames and piece them together in whatever way we want. That is great for constructing custom UIs like error dialogs. Unfortunately, we can't take custom stacks and set them in the native UIs like Chrome DevTools. The nice thing about `console.createTask()` is that the resulting stacks are natively integrated into the Chrome DevTools in the console and the breakpoint debugger. They also automatically follow source mapping and ignoreLists. The downside is that there's no way to extract the async stack outside the native UI itself so this information cannot be used for custom UIs like errors dialogs. It also means we can't collect this on the server and then pass it to the client for server components. The solution here is that we use both techniques and collect both an `Error` object and a `Task` object for every JSX call. The main concern about this approach is the performance so that's the main thing to test. It's certainly too slow for production but it might also be too slow even for DEV. This first PR doesn't actually use the stacks yet. It just collects them as the first step. The next step is to start utilizing this information in error printing etc. For RSC we pass the stack along across over the wire. This can be concatenated on the client following the owner path to create an owner stack leading back into the server. We'll later use this information to restore fake frames on the client for native integration. Since this information quickly gets pretty heavy if we include all frames, we strip out the top frame. We also strip out everything below the functions that call into user space in the Flight runtime. To do this we need to figure out the frames that represents calling out into user space. The resulting stack is typically just the one frame inside the owner component's JSX callsite. I also eagerly strip out things we expect to be ignoreList:ed anyway - such as `node_modules` and Node.js internals.
195 lines
4.9 KiB
JavaScript
195 lines
4.9 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
export type ReactNode =
|
|
| React$Element<any>
|
|
| ReactPortal
|
|
| ReactText
|
|
| ReactFragment
|
|
| ReactProvider<any>
|
|
| ReactConsumer<any>;
|
|
|
|
export type ReactEmpty = null | void | boolean;
|
|
|
|
export type ReactFragment = ReactEmpty | Iterable<React$Node>;
|
|
|
|
export type ReactNodeList = ReactEmpty | React$Node;
|
|
|
|
export type ReactText = string | number;
|
|
|
|
export type ReactProvider<T> = {
|
|
$$typeof: symbol | number,
|
|
type: ReactContext<T>,
|
|
key: null | string,
|
|
ref: null,
|
|
props: {
|
|
value: T,
|
|
children?: ReactNodeList,
|
|
},
|
|
};
|
|
|
|
export type ReactConsumerType<T> = {
|
|
$$typeof: symbol | number,
|
|
_context: ReactContext<T>,
|
|
};
|
|
|
|
export type ReactConsumer<T> = {
|
|
$$typeof: symbol | number,
|
|
type: ReactConsumerType<T>,
|
|
key: null | string,
|
|
ref: null,
|
|
props: {
|
|
children: (value: T) => ReactNodeList,
|
|
},
|
|
};
|
|
|
|
export type ReactContext<T> = {
|
|
$$typeof: symbol | number,
|
|
Consumer: ReactConsumerType<T>,
|
|
Provider: ReactContext<T>,
|
|
_currentValue: T,
|
|
_currentValue2: T,
|
|
_threadCount: number,
|
|
// DEV only
|
|
_currentRenderer?: Object | null,
|
|
_currentRenderer2?: Object | null,
|
|
// This value may be added by application code
|
|
// to improve DEV tooling display names
|
|
displayName?: string,
|
|
};
|
|
|
|
export type ReactPortal = {
|
|
$$typeof: symbol | number,
|
|
key: null | string,
|
|
containerInfo: any,
|
|
children: ReactNodeList,
|
|
// TODO: figure out the API for cross-renderer implementation.
|
|
implementation: any,
|
|
};
|
|
|
|
export type RefObject = {
|
|
current: any,
|
|
};
|
|
|
|
export type ReactScope = {
|
|
$$typeof: symbol | number,
|
|
};
|
|
|
|
export type ReactScopeQuery = (
|
|
type: string,
|
|
props: {[string]: mixed},
|
|
instance: mixed,
|
|
) => boolean;
|
|
|
|
export type ReactScopeInstance = {
|
|
DO_NOT_USE_queryAllNodes(ReactScopeQuery): null | Array<Object>,
|
|
DO_NOT_USE_queryFirstNode(ReactScopeQuery): null | Object,
|
|
containsNode(Object): boolean,
|
|
getChildContextValues: <T>(context: ReactContext<T>) => Array<T>,
|
|
};
|
|
|
|
// The subset of a Thenable required by things thrown by Suspense.
|
|
// This doesn't require a value to be passed to either handler.
|
|
export interface Wakeable {
|
|
then(onFulfill: () => mixed, onReject: () => mixed): void | Wakeable;
|
|
}
|
|
|
|
// The subset of a Promise that React APIs rely on. This resolves a value.
|
|
// This doesn't require a return value neither from the handler nor the
|
|
// then function.
|
|
interface ThenableImpl<T> {
|
|
then(
|
|
onFulfill: (value: T) => mixed,
|
|
onReject: (error: mixed) => mixed,
|
|
): void | Wakeable;
|
|
}
|
|
interface UntrackedThenable<T> extends ThenableImpl<T> {
|
|
status?: void;
|
|
_debugInfo?: null | ReactDebugInfo;
|
|
}
|
|
|
|
export interface PendingThenable<T> extends ThenableImpl<T> {
|
|
status: 'pending';
|
|
_debugInfo?: null | ReactDebugInfo;
|
|
}
|
|
|
|
export interface FulfilledThenable<T> extends ThenableImpl<T> {
|
|
status: 'fulfilled';
|
|
value: T;
|
|
_debugInfo?: null | ReactDebugInfo;
|
|
}
|
|
|
|
export interface RejectedThenable<T> extends ThenableImpl<T> {
|
|
status: 'rejected';
|
|
reason: mixed;
|
|
_debugInfo?: null | ReactDebugInfo;
|
|
}
|
|
|
|
export type Thenable<T> =
|
|
| UntrackedThenable<T>
|
|
| PendingThenable<T>
|
|
| FulfilledThenable<T>
|
|
| RejectedThenable<T>;
|
|
|
|
export type OffscreenMode =
|
|
| 'hidden'
|
|
| 'unstable-defer-without-hiding'
|
|
| 'visible'
|
|
| 'manual';
|
|
|
|
export type StartTransitionOptions = {
|
|
name?: string,
|
|
};
|
|
|
|
export type Usable<T> = Thenable<T> | ReactContext<T>;
|
|
|
|
export type ReactCustomFormAction = {
|
|
name?: string,
|
|
action?: string,
|
|
encType?: string,
|
|
method?: string,
|
|
target?: string,
|
|
data?: null | FormData,
|
|
};
|
|
|
|
// This is an opaque type returned by decodeFormState on the server, but it's
|
|
// defined in this shared file because the same type is used by React on
|
|
// the client.
|
|
export type ReactFormState<S, ReferenceId> = [
|
|
S /* actual state value */,
|
|
string /* key path */,
|
|
ReferenceId /* Server Reference ID */,
|
|
number /* number of bound arguments */,
|
|
];
|
|
|
|
export type Awaited<T> = T extends null | void
|
|
? T // special case for `null | undefined` when not in `--strictNullChecks` mode
|
|
: T extends Object // `await` only unwraps object types with a callable then. Non-object types are not unwrapped.
|
|
? T extends {then(onfulfilled: infer F): any} // thenable, extracts the first argument to `then()`
|
|
? F extends (value: infer V) => any // if the argument to `then` is callable, extracts the argument
|
|
? Awaited<V> // recursively unwrap the value
|
|
: empty // the argument to `then` was not callable.
|
|
: T // argument was not an object
|
|
: T; // non-thenable
|
|
|
|
export type ReactComponentInfo = {
|
|
+name?: string,
|
|
+env?: string,
|
|
+owner?: null | ReactComponentInfo,
|
|
+stack?: null | string,
|
|
};
|
|
|
|
export type ReactAsyncInfo = {
|
|
+started?: number,
|
|
+completed?: number,
|
|
+stack?: string,
|
|
};
|
|
|
|
export type ReactDebugInfo = Array<ReactComponentInfo | ReactAsyncInfo>;
|