mirror of
https://github.com/facebook/react.git
synced 2026-02-26 07:55:55 +00:00
[DevTools] Rename Fiber to Element in the Bridge Protocol and RendererInterface (#30490)
I need to start clarifying where things are really actually Fibers and where they're not since I'm adding Server Components as a separate type of component instance which is not backed by a Fiber. Nothing in the front end should really know anything about what kind of renderer implementation we're inspecting and indeed it's already not always a "Fiber" in the legacy renderer. We typically refer to this as a "Component Instance" but the front end currently refers to it as an Element as it historically grew from the browser DevTools Elements tab. I also moved the renderer.js implementation into the `backend/fiber` folder. These are at the same level as `backend/legacy`. This clarifies that anything outside of this folder ideally shouldn't refer to a "Fiber". console.js and profilingHooks.js unfortunately use Fibers a lot which needs further refactoring. The profiler frontend also uses the term alot.
This commit is contained in:
committed by
GitHub
parent
941e1b4a0a
commit
ec98d36c3a
292
packages/react-devtools-shared/src/backend/shared/DevToolsComponentStackFrame.js
vendored
Normal file
292
packages/react-devtools-shared/src/backend/shared/DevToolsComponentStackFrame.js
vendored
Normal file
@@ -0,0 +1,292 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
// This is a DevTools fork of ReactComponentStackFrame.
|
||||
// This fork enables DevTools to use the same "native" component stack format,
|
||||
// while still maintaining support for multiple renderer versions
|
||||
// (which use different values for ReactTypeOfWork).
|
||||
|
||||
import type {CurrentDispatcherRef} from '../types';
|
||||
|
||||
// The shared console patching code is DEV-only.
|
||||
// We can't use it since DevTools only ships production builds.
|
||||
import {disableLogs, reenableLogs} from './DevToolsConsolePatching';
|
||||
|
||||
let prefix;
|
||||
export function describeBuiltInComponentFrame(name: string): string {
|
||||
if (prefix === undefined) {
|
||||
// Extract the VM specific prefix used by each line.
|
||||
try {
|
||||
throw Error();
|
||||
} catch (x) {
|
||||
const match = x.stack.trim().match(/\n( *(at )?)/);
|
||||
prefix = (match && match[1]) || '';
|
||||
}
|
||||
}
|
||||
let suffix = '';
|
||||
if (__IS_CHROME__ || __IS_EDGE__) {
|
||||
suffix = ' (<anonymous>)';
|
||||
} else if (__IS_FIREFOX__) {
|
||||
suffix = '@unknown:0:0';
|
||||
}
|
||||
// We use the prefix to ensure our stacks line up with native stack frames.
|
||||
// We use a suffix to ensure it gets parsed natively.
|
||||
return '\n' + prefix + name + suffix;
|
||||
}
|
||||
|
||||
export function describeDebugInfoFrame(name: string, env: ?string): string {
|
||||
return describeBuiltInComponentFrame(name + (env ? ' [' + env + ']' : ''));
|
||||
}
|
||||
|
||||
let reentry = false;
|
||||
let componentFrameCache;
|
||||
if (__DEV__) {
|
||||
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
|
||||
componentFrameCache = new PossiblyWeakMap<$FlowFixMe, string>();
|
||||
}
|
||||
|
||||
export function describeNativeComponentFrame(
|
||||
fn: Function,
|
||||
construct: boolean,
|
||||
currentDispatcherRef: CurrentDispatcherRef,
|
||||
): string {
|
||||
// If something asked for a stack inside a fake render, it should get ignored.
|
||||
if (!fn || reentry) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
const frame = componentFrameCache.get(fn);
|
||||
if (frame !== undefined) {
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
|
||||
const previousPrepareStackTrace = Error.prepareStackTrace;
|
||||
// $FlowFixMe[incompatible-type] It does accept undefined.
|
||||
Error.prepareStackTrace = undefined;
|
||||
|
||||
reentry = true;
|
||||
|
||||
// Override the dispatcher so effects scheduled by this shallow render are thrown away.
|
||||
//
|
||||
// Note that unlike the code this was forked from (in ReactComponentStackFrame)
|
||||
// DevTools should override the dispatcher even when DevTools is compiled in production mode,
|
||||
// because the app itself may be in development mode and log errors/warnings.
|
||||
const previousDispatcher = currentDispatcherRef.H;
|
||||
currentDispatcherRef.H = null;
|
||||
disableLogs();
|
||||
|
||||
// NOTE: keep in sync with the implementation in ReactComponentStackFrame
|
||||
|
||||
/**
|
||||
* Finding a common stack frame between sample and control errors can be
|
||||
* tricky given the different types and levels of stack trace truncation from
|
||||
* different JS VMs. So instead we'll attempt to control what that common
|
||||
* frame should be through this object method:
|
||||
* Having both the sample and control errors be in the function under the
|
||||
* `DescribeNativeComponentFrameRoot` property, + setting the `name` and
|
||||
* `displayName` properties of the function ensures that a stack
|
||||
* frame exists that has the method name `DescribeNativeComponentFrameRoot` in
|
||||
* it for both control and sample stacks.
|
||||
*/
|
||||
const RunInRootFrame = {
|
||||
DetermineComponentFrameRoot(): [?string, ?string] {
|
||||
let control;
|
||||
try {
|
||||
// This should throw.
|
||||
if (construct) {
|
||||
// Something should be setting the props in the constructor.
|
||||
const Fake = function () {
|
||||
throw Error();
|
||||
};
|
||||
// $FlowFixMe[prop-missing]
|
||||
Object.defineProperty(Fake.prototype, 'props', {
|
||||
set: function () {
|
||||
// We use a throwing setter instead of frozen or non-writable props
|
||||
// because that won't throw in a non-strict mode function.
|
||||
throw Error();
|
||||
},
|
||||
});
|
||||
if (typeof Reflect === 'object' && Reflect.construct) {
|
||||
// We construct a different control for this case to include any extra
|
||||
// frames added by the construct call.
|
||||
try {
|
||||
Reflect.construct(Fake, []);
|
||||
} catch (x) {
|
||||
control = x;
|
||||
}
|
||||
Reflect.construct(fn, [], Fake);
|
||||
} else {
|
||||
try {
|
||||
Fake.call();
|
||||
} catch (x) {
|
||||
control = x;
|
||||
}
|
||||
// $FlowFixMe[prop-missing] found when upgrading Flow
|
||||
fn.call(Fake.prototype);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
throw Error();
|
||||
} catch (x) {
|
||||
control = x;
|
||||
}
|
||||
// TODO(luna): This will currently only throw if the function component
|
||||
// tries to access React/ReactDOM/props. We should probably make this throw
|
||||
// in simple components too
|
||||
const maybePromise = fn();
|
||||
|
||||
// If the function component returns a promise, it's likely an async
|
||||
// component, which we don't yet support. Attach a noop catch handler to
|
||||
// silence the error.
|
||||
// TODO: Implement component stacks for async client components?
|
||||
if (maybePromise && typeof maybePromise.catch === 'function') {
|
||||
maybePromise.catch(() => {});
|
||||
}
|
||||
}
|
||||
} catch (sample) {
|
||||
// This is inlined manually because closure doesn't do it for us.
|
||||
if (sample && control && typeof sample.stack === 'string') {
|
||||
return [sample.stack, control.stack];
|
||||
}
|
||||
}
|
||||
return [null, null];
|
||||
},
|
||||
};
|
||||
// $FlowFixMe[prop-missing]
|
||||
RunInRootFrame.DetermineComponentFrameRoot.displayName =
|
||||
'DetermineComponentFrameRoot';
|
||||
const namePropDescriptor = Object.getOwnPropertyDescriptor(
|
||||
RunInRootFrame.DetermineComponentFrameRoot,
|
||||
'name',
|
||||
);
|
||||
// Before ES6, the `name` property was not configurable.
|
||||
if (namePropDescriptor && namePropDescriptor.configurable) {
|
||||
// V8 utilizes a function's `name` property when generating a stack trace.
|
||||
Object.defineProperty(
|
||||
RunInRootFrame.DetermineComponentFrameRoot,
|
||||
// Configurable properties can be updated even if its writable descriptor
|
||||
// is set to `false`.
|
||||
// $FlowFixMe[cannot-write]
|
||||
'name',
|
||||
{value: 'DetermineComponentFrameRoot'},
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const [sampleStack, controlStack] =
|
||||
RunInRootFrame.DetermineComponentFrameRoot();
|
||||
if (sampleStack && controlStack) {
|
||||
// This extracts the first frame from the sample that isn't also in the control.
|
||||
// Skipping one frame that we assume is the frame that calls the two.
|
||||
const sampleLines = sampleStack.split('\n');
|
||||
const controlLines = controlStack.split('\n');
|
||||
let s = 0;
|
||||
let c = 0;
|
||||
while (
|
||||
s < sampleLines.length &&
|
||||
!sampleLines[s].includes('DetermineComponentFrameRoot')
|
||||
) {
|
||||
s++;
|
||||
}
|
||||
while (
|
||||
c < controlLines.length &&
|
||||
!controlLines[c].includes('DetermineComponentFrameRoot')
|
||||
) {
|
||||
c++;
|
||||
}
|
||||
// We couldn't find our intentionally injected common root frame, attempt
|
||||
// to find another common root frame by search from the bottom of the
|
||||
// control stack...
|
||||
if (s === sampleLines.length || c === controlLines.length) {
|
||||
s = sampleLines.length - 1;
|
||||
c = controlLines.length - 1;
|
||||
while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) {
|
||||
// We expect at least one stack frame to be shared.
|
||||
// Typically this will be the root most one. However, stack frames may be
|
||||
// cut off due to maximum stack limits. In this case, one maybe cut off
|
||||
// earlier than the other. We assume that the sample is longer or the same
|
||||
// and there for cut off earlier. So we should find the root most frame in
|
||||
// the sample somewhere in the control.
|
||||
c--;
|
||||
}
|
||||
}
|
||||
for (; s >= 1 && c >= 0; s--, c--) {
|
||||
// Next we find the first one that isn't the same which should be the
|
||||
// frame that called our sample function and the control.
|
||||
if (sampleLines[s] !== controlLines[c]) {
|
||||
// In V8, the first line is describing the message but other VMs don't.
|
||||
// If we're about to return the first line, and the control is also on the same
|
||||
// line, that's a pretty good indicator that our sample threw at same line as
|
||||
// the control. I.e. before we entered the sample frame. So we ignore this result.
|
||||
// This can happen if you passed a class to function component, or non-function.
|
||||
if (s !== 1 || c !== 1) {
|
||||
do {
|
||||
s--;
|
||||
c--;
|
||||
// We may still have similar intermediate frames from the construct call.
|
||||
// The next one that isn't the same should be our match though.
|
||||
if (c < 0 || sampleLines[s] !== controlLines[c]) {
|
||||
// V8 adds a "new" prefix for native classes. Let's remove it to make it prettier.
|
||||
let frame = '\n' + sampleLines[s].replace(' at new ', ' at ');
|
||||
|
||||
// If our component frame is labeled "<anonymous>"
|
||||
// but we have a user-provided "displayName"
|
||||
// splice it in to make the stack more readable.
|
||||
if (fn.displayName && frame.includes('<anonymous>')) {
|
||||
frame = frame.replace('<anonymous>', fn.displayName);
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
if (typeof fn === 'function') {
|
||||
componentFrameCache.set(fn, frame);
|
||||
}
|
||||
}
|
||||
// Return the line we found.
|
||||
return frame;
|
||||
}
|
||||
} while (s >= 1 && c >= 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
reentry = false;
|
||||
|
||||
Error.prepareStackTrace = previousPrepareStackTrace;
|
||||
|
||||
currentDispatcherRef.H = previousDispatcher;
|
||||
reenableLogs();
|
||||
}
|
||||
// Fallback to just using the name if we couldn't make it throw.
|
||||
const name = fn ? fn.displayName || fn.name : '';
|
||||
const syntheticFrame = name ? describeBuiltInComponentFrame(name) : '';
|
||||
if (__DEV__) {
|
||||
if (typeof fn === 'function') {
|
||||
componentFrameCache.set(fn, syntheticFrame);
|
||||
}
|
||||
}
|
||||
return syntheticFrame;
|
||||
}
|
||||
|
||||
export function describeClassComponentFrame(
|
||||
ctor: Function,
|
||||
currentDispatcherRef: CurrentDispatcherRef,
|
||||
): string {
|
||||
return describeNativeComponentFrame(ctor, true, currentDispatcherRef);
|
||||
}
|
||||
|
||||
export function describeFunctionComponentFrame(
|
||||
fn: Function,
|
||||
currentDispatcherRef: CurrentDispatcherRef,
|
||||
): string {
|
||||
return describeNativeComponentFrame(fn, false, currentDispatcherRef);
|
||||
}
|
||||
Reference in New Issue
Block a user