mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Port complete
This commit is contained in:
@@ -369,7 +369,7 @@ server.tool(
|
||||
text: z.string(),
|
||||
},
|
||||
async ({text}) => {
|
||||
const componentTree = await parseReactComponentTree(text);
|
||||
const componentTree = await parseReactComponentTree();
|
||||
|
||||
return {
|
||||
content: [
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import * as babel from '@babel/core';
|
||||
import puppeteer from 'puppeteer';
|
||||
import {readFileSync} from 'fs';
|
||||
import * as path from 'path';
|
||||
// @ts-ignore
|
||||
import * as babelPresetTypescript from '@babel/preset-typescript';
|
||||
// @ts-ignore
|
||||
import * as babelPresetEnv from '@babel/preset-env';
|
||||
// @ts-ignore
|
||||
import * as babelPresetReact from '@babel/preset-react';
|
||||
import extractComponentTreeFromDevTools from '../utils/reactDevTools/extractComponentTree';
|
||||
// import {generateComponentTree} from '../utils/reactDevTools/reactDevTools';
|
||||
|
||||
function delay(time: number) {
|
||||
return new Promise(resolve => {
|
||||
@@ -15,7 +8,7 @@ function delay(time: number) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function parseReactComponentTree(code: string): Promise<string> {
|
||||
export async function parseReactComponentTree(): Promise<string> {
|
||||
const browser = await puppeteer.connect({
|
||||
browserURL: 'http://127.0.0.1:9222',
|
||||
defaultViewport: null,
|
||||
@@ -27,15 +20,23 @@ export async function parseReactComponentTree(code: string): Promise<string> {
|
||||
for (const page of pages) {
|
||||
const url = await page.url();
|
||||
|
||||
if (url.startsWith('https://react.dev')) {
|
||||
if (url.startsWith('http://localhost:3000')) {
|
||||
localhostPage = page;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (localhostPage) {
|
||||
const devtoolsHook = await localhostPage.evaluate(getReactComponentTree);
|
||||
console.log(devtoolsHook);
|
||||
const devtoolsHook = await localhostPage.evaluate(
|
||||
() => (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__,
|
||||
);
|
||||
|
||||
try {
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
extractComponentTreeFromDevTools(devtoolsHook);
|
||||
|
||||
return new Promise(resolve => resolve(JSON.stringify(devtoolsHook)));
|
||||
} else {
|
||||
@@ -43,36 +44,6 @@ export async function parseReactComponentTree(code: string): Promise<string> {
|
||||
}
|
||||
}
|
||||
|
||||
function getReactComponentTree() {
|
||||
// Check if the React DevTools hook is available
|
||||
const hook: any = (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
if (!hook) {
|
||||
console.error(
|
||||
'React DevTools hook is not available. Make sure React DevTools extension is installed.',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the first renderer from the DevTools hook
|
||||
const renderers: any = Array.from(hook.renderers.values());
|
||||
|
||||
// return renderers;
|
||||
|
||||
if (renderers.length === 0) {
|
||||
console.error('No React renderers found.');
|
||||
return null;
|
||||
}
|
||||
|
||||
const rootFiber = Array.from(
|
||||
(window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__.getFiberRoots(1),
|
||||
)[0];
|
||||
|
||||
//ROOT FIBER
|
||||
if (!rootFiber) {
|
||||
return 'error';
|
||||
}
|
||||
}
|
||||
|
||||
parseReactComponentTree('')
|
||||
parseReactComponentTree()
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.error(error));
|
||||
|
||||
@@ -1,399 +0,0 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
// Define ReactComponentInfo type directly to avoid import issues
|
||||
type ReactComponentInfo = {
|
||||
name: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
// Constants for instance types
|
||||
const FIBER_INSTANCE = 0;
|
||||
const FILTERED_FIBER_INSTANCE = 2;
|
||||
const VIRTUAL_INSTANCE = 1;
|
||||
|
||||
// Only keeping the constants needed for tree traversal
|
||||
|
||||
// Fiber tags - only keeping the ones used in getDisplayNameForFiber
|
||||
const FunctionComponent = 0;
|
||||
const ClassComponent = 1;
|
||||
const IndeterminateComponent = 2;
|
||||
const HostRoot = 3;
|
||||
const HostPortal = 4;
|
||||
const HostComponent = 5;
|
||||
const HostText = 6;
|
||||
const Fragment = 7;
|
||||
const ForwardRef = 11;
|
||||
const Profiler = 12;
|
||||
const SuspenseComponent = 13;
|
||||
const MemoComponent = 14;
|
||||
const SimpleMemoComponent = 15;
|
||||
const LazyComponent = 16;
|
||||
const IncompleteClassComponent = 17;
|
||||
const SuspenseListComponent = 19;
|
||||
const ScopeComponent = 21;
|
||||
const OffscreenComponent = 22;
|
||||
const LegacyHiddenComponent = 23;
|
||||
const CacheComponent = 24;
|
||||
const TracingMarkerComponent = 25;
|
||||
const HostHoistable = 26;
|
||||
const HostSingleton = 27;
|
||||
const IncompleteFunctionComponent = 28;
|
||||
const Throw = 29;
|
||||
const ViewTransitionComponent = 30;
|
||||
const ActivityComponent = 31;
|
||||
|
||||
// Symbol constants
|
||||
const CONCURRENT_MODE_NUMBER = 0xeacf;
|
||||
const CONCURRENT_MODE_SYMBOL_STRING = 'Symbol(react.concurrent_mode)';
|
||||
const DEPRECATED_ASYNC_MODE_SYMBOL_STRING = 'Symbol(react.async_mode)';
|
||||
const PROVIDER_NUMBER = 0xeacd;
|
||||
const PROVIDER_SYMBOL_STRING = 'Symbol(react.provider)';
|
||||
const CONTEXT_NUMBER = 0xeace;
|
||||
const CONTEXT_SYMBOL_STRING = 'Symbol(react.context)';
|
||||
const SERVER_CONTEXT_SYMBOL_STRING = 'Symbol(react.server_context)';
|
||||
const CONSUMER_SYMBOL_STRING = 'Symbol(react.consumer)';
|
||||
const STRICT_MODE_NUMBER = 0xeacc;
|
||||
const STRICT_MODE_SYMBOL_STRING = 'Symbol(react.strict_mode)';
|
||||
const PROFILER_NUMBER = 0xead2;
|
||||
const PROFILER_SYMBOL_STRING = 'Symbol(react.profiler)';
|
||||
const SCOPE_NUMBER = 0xead7;
|
||||
const SCOPE_SYMBOL_STRING = 'Symbol(react.scope)';
|
||||
// Define a string key for REACT_MEMO_CACHE_SENTINEL to fix type issues
|
||||
const REACT_MEMO_CACHE_SENTINEL = 'REACT_MEMO_CACHE_SENTINEL';
|
||||
|
||||
// Type definitions
|
||||
type Fiber = {
|
||||
tag: number;
|
||||
elementType: any;
|
||||
type: any;
|
||||
stateNode: any;
|
||||
memoizedProps: any;
|
||||
memoizedState: any;
|
||||
updateQueue: any;
|
||||
key: string | null;
|
||||
_debugID?: number;
|
||||
_debugOwner?: any;
|
||||
_debugStack?: any;
|
||||
child: Fiber | null;
|
||||
sibling: Fiber | null;
|
||||
};
|
||||
|
||||
type Source = {
|
||||
fileName: string;
|
||||
lineNumber: number;
|
||||
columnNumber?: number;
|
||||
};
|
||||
|
||||
type FiberInstance = {
|
||||
kind: 0;
|
||||
id: number;
|
||||
parent: null | DevToolsInstance;
|
||||
firstChild: null | DevToolsInstance;
|
||||
nextSibling: null | DevToolsInstance;
|
||||
source: null | string | Error | Source;
|
||||
logCount: number;
|
||||
treeBaseDuration: number;
|
||||
data: Fiber;
|
||||
};
|
||||
|
||||
type FilteredFiberInstance = {
|
||||
kind: 2;
|
||||
id: number;
|
||||
parent: null | DevToolsInstance;
|
||||
firstChild: null | DevToolsInstance;
|
||||
nextSibling: null | DevToolsInstance;
|
||||
source: null | string | Error | Source;
|
||||
logCount: number;
|
||||
treeBaseDuration: number;
|
||||
data: Fiber;
|
||||
};
|
||||
|
||||
type VirtualInstance = {
|
||||
kind: 1;
|
||||
id: number;
|
||||
parent: null | DevToolsInstance;
|
||||
firstChild: null | DevToolsInstance;
|
||||
nextSibling: null | DevToolsInstance;
|
||||
source: null | string | Error | Source;
|
||||
logCount: number;
|
||||
treeBaseDuration: number;
|
||||
data: ReactComponentInfo;
|
||||
};
|
||||
|
||||
type DevToolsInstance = FiberInstance | FilteredFiberInstance | VirtualInstance;
|
||||
|
||||
// Helper functions
|
||||
function getUID(): number {
|
||||
return Math.floor(Math.random() * 1000000);
|
||||
}
|
||||
|
||||
function getTypeSymbol(type: any): symbol | number | string | null {
|
||||
const symbolOrNumber =
|
||||
typeof type === 'object' && type !== null ? type.$$typeof : null;
|
||||
return symbolOrNumber;
|
||||
}
|
||||
|
||||
function resolveFiberType(type: any): any {
|
||||
return type.type || type;
|
||||
}
|
||||
|
||||
function getDisplayName(type: any): string | null {
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let displayName = null;
|
||||
if (typeof type.displayName === 'string') {
|
||||
displayName = type.displayName;
|
||||
} else if (typeof type.name === 'string' && type.name !== '') {
|
||||
displayName = type.name;
|
||||
}
|
||||
|
||||
return displayName || 'Anonymous';
|
||||
}
|
||||
|
||||
function getWrappedDisplayName(
|
||||
outerType: any,
|
||||
innerType: any,
|
||||
wrapperName: string,
|
||||
fallbackName: string,
|
||||
): string {
|
||||
const displayName = getDisplayName(innerType);
|
||||
return displayName ? `${wrapperName}(${displayName})` : fallbackName;
|
||||
}
|
||||
|
||||
// Instance creation functions
|
||||
function createFiberInstance(fiber: Fiber): FiberInstance {
|
||||
return {
|
||||
kind: FIBER_INSTANCE,
|
||||
id: getUID(),
|
||||
parent: null,
|
||||
firstChild: null,
|
||||
nextSibling: null,
|
||||
source: null,
|
||||
logCount: 0,
|
||||
treeBaseDuration: 0,
|
||||
data: fiber,
|
||||
};
|
||||
}
|
||||
|
||||
function createFilteredFiberInstance(fiber: Fiber): FilteredFiberInstance {
|
||||
return {
|
||||
kind: FILTERED_FIBER_INSTANCE,
|
||||
id: 0,
|
||||
parent: null,
|
||||
firstChild: null,
|
||||
nextSibling: null,
|
||||
source: null,
|
||||
logCount: 0,
|
||||
treeBaseDuration: 0,
|
||||
data: fiber,
|
||||
};
|
||||
}
|
||||
|
||||
function createVirtualInstance(
|
||||
debugEntry: ReactComponentInfo,
|
||||
): VirtualInstance {
|
||||
return {
|
||||
kind: VIRTUAL_INSTANCE,
|
||||
id: getUID(),
|
||||
parent: null,
|
||||
firstChild: null,
|
||||
nextSibling: null,
|
||||
source: null,
|
||||
logCount: 0,
|
||||
treeBaseDuration: 0,
|
||||
data: debugEntry,
|
||||
};
|
||||
}
|
||||
|
||||
// Main functions for tree traversal
|
||||
function getDisplayNameForFiber(
|
||||
fiber: Fiber,
|
||||
shouldSkipForgetCheck: boolean = false,
|
||||
): string | null {
|
||||
const {elementType, type, tag} = fiber;
|
||||
|
||||
let resolvedType = type;
|
||||
if (typeof type === 'object' && type !== null) {
|
||||
resolvedType = resolveFiberType(type);
|
||||
}
|
||||
|
||||
let resolvedContext: any = null;
|
||||
if (
|
||||
!shouldSkipForgetCheck &&
|
||||
(fiber.updateQueue?.memoCache != null ||
|
||||
(Array.isArray(fiber.memoizedState?.memoizedState) &&
|
||||
fiber.memoizedState.memoizedState[0]?.[REACT_MEMO_CACHE_SENTINEL]) ||
|
||||
fiber.memoizedState?.memoizedState?.[REACT_MEMO_CACHE_SENTINEL])
|
||||
) {
|
||||
const displayNameWithoutForgetWrapper = getDisplayNameForFiber(fiber, true);
|
||||
if (displayNameWithoutForgetWrapper == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `Forget(${displayNameWithoutForgetWrapper})`;
|
||||
}
|
||||
|
||||
switch (tag) {
|
||||
case ActivityComponent:
|
||||
return 'Activity';
|
||||
case CacheComponent:
|
||||
return 'Cache';
|
||||
case ClassComponent:
|
||||
case IncompleteClassComponent:
|
||||
case IncompleteFunctionComponent:
|
||||
case FunctionComponent:
|
||||
case IndeterminateComponent:
|
||||
return getDisplayName(resolvedType);
|
||||
case ForwardRef:
|
||||
return getWrappedDisplayName(
|
||||
elementType,
|
||||
resolvedType,
|
||||
'ForwardRef',
|
||||
'Anonymous',
|
||||
);
|
||||
case HostRoot:
|
||||
const fiberRoot = fiber.stateNode;
|
||||
if (fiberRoot != null && fiberRoot._debugRootType !== null) {
|
||||
return fiberRoot._debugRootType;
|
||||
}
|
||||
return null;
|
||||
case HostComponent:
|
||||
case HostSingleton:
|
||||
case HostHoistable:
|
||||
return type;
|
||||
case HostPortal:
|
||||
case HostText:
|
||||
return null;
|
||||
case Fragment:
|
||||
return 'Fragment';
|
||||
case LazyComponent:
|
||||
return 'Lazy';
|
||||
case MemoComponent:
|
||||
case SimpleMemoComponent:
|
||||
return getWrappedDisplayName(
|
||||
elementType,
|
||||
resolvedType,
|
||||
'Memo',
|
||||
'Anonymous',
|
||||
);
|
||||
case SuspenseComponent:
|
||||
return 'Suspense';
|
||||
case LegacyHiddenComponent:
|
||||
return 'LegacyHidden';
|
||||
case OffscreenComponent:
|
||||
return 'Offscreen';
|
||||
case ScopeComponent:
|
||||
return 'Scope';
|
||||
case SuspenseListComponent:
|
||||
return 'SuspenseList';
|
||||
case Profiler:
|
||||
return 'Profiler';
|
||||
case TracingMarkerComponent:
|
||||
return 'TracingMarker';
|
||||
case ViewTransitionComponent:
|
||||
return 'ViewTransition';
|
||||
case Throw:
|
||||
return 'Error';
|
||||
default:
|
||||
const typeSymbol = getTypeSymbol(type);
|
||||
|
||||
switch (typeSymbol) {
|
||||
case CONCURRENT_MODE_NUMBER:
|
||||
case CONCURRENT_MODE_SYMBOL_STRING:
|
||||
case DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
|
||||
return null;
|
||||
case PROVIDER_NUMBER:
|
||||
case PROVIDER_SYMBOL_STRING:
|
||||
resolvedContext = fiber.type._context || fiber.type.context;
|
||||
return `${resolvedContext.displayName || 'Context'}.Provider`;
|
||||
case CONTEXT_NUMBER:
|
||||
case CONTEXT_SYMBOL_STRING:
|
||||
case SERVER_CONTEXT_SYMBOL_STRING:
|
||||
if (
|
||||
fiber.type._context === undefined &&
|
||||
fiber.type.Provider === fiber.type
|
||||
) {
|
||||
resolvedContext = fiber.type;
|
||||
return `${resolvedContext.displayName || 'Context'}.Provider`;
|
||||
}
|
||||
resolvedContext = fiber.type._context || fiber.type;
|
||||
return `${resolvedContext.displayName || 'Context'}.Consumer`;
|
||||
case CONSUMER_SYMBOL_STRING:
|
||||
resolvedContext = fiber.type._context;
|
||||
return `${resolvedContext.displayName || 'Context'}.Consumer`;
|
||||
case STRICT_MODE_NUMBER:
|
||||
case STRICT_MODE_SYMBOL_STRING:
|
||||
return null;
|
||||
case PROFILER_NUMBER:
|
||||
case PROFILER_SYMBOL_STRING:
|
||||
return `Profiler(${fiber.memoizedProps.id})`;
|
||||
case SCOPE_NUMBER:
|
||||
case SCOPE_SYMBOL_STRING:
|
||||
return 'Scope';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function debugTree(instance: DevToolsInstance, indent: number = 0) {
|
||||
const name =
|
||||
(instance.kind !== VIRTUAL_INSTANCE
|
||||
? getDisplayNameForFiber(instance.data)
|
||||
: instance.data.name) || '';
|
||||
console.log(
|
||||
' '.repeat(indent) +
|
||||
'- ' +
|
||||
(instance.kind === FILTERED_FIBER_INSTANCE ? 0 : instance.id) +
|
||||
' (' +
|
||||
name +
|
||||
')',
|
||||
'parent',
|
||||
instance.parent === null
|
||||
? ' '
|
||||
: instance.parent.kind === FILTERED_FIBER_INSTANCE
|
||||
? 0
|
||||
: instance.parent.id,
|
||||
'next',
|
||||
instance.nextSibling === null ? ' ' : instance.nextSibling.id,
|
||||
);
|
||||
let child = instance.firstChild;
|
||||
while (child !== null) {
|
||||
debugTree(child, indent + 1);
|
||||
child = child.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to insert a child into the tree
|
||||
function insertChild(child: DevToolsInstance) {
|
||||
if (child.parent !== null) {
|
||||
throw new Error('Child already has a parent');
|
||||
}
|
||||
|
||||
child.parent = null;
|
||||
child.nextSibling = null;
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
// Export the functions needed for tree traversal
|
||||
export {
|
||||
debugTree,
|
||||
getDisplayNameForFiber,
|
||||
createFiberInstance,
|
||||
createFilteredFiberInstance,
|
||||
createVirtualInstance,
|
||||
insertChild,
|
||||
FIBER_INSTANCE,
|
||||
FILTERED_FIBER_INSTANCE,
|
||||
VIRTUAL_INSTANCE,
|
||||
};
|
||||
@@ -0,0 +1,310 @@
|
||||
import {generateComponentTree} from './reactDevTools';
|
||||
import {Fiber} from './reactDevToolsTypes';
|
||||
|
||||
/**
|
||||
* Interface representing a node in the component tree
|
||||
*/
|
||||
export interface ComponentTreeNode {
|
||||
id: number;
|
||||
name: string;
|
||||
type: string | number;
|
||||
key: string | null;
|
||||
children: ComponentTreeNode[];
|
||||
props?: Record<string, any> | undefined;
|
||||
state?: Record<string, any> | undefined;
|
||||
fiber?: Fiber | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the component tree from the React DevTools implementation
|
||||
* @param renderer The React renderer
|
||||
* @param fiber The root fiber to start from
|
||||
* @returns The component tree as a structured object
|
||||
*/
|
||||
export function extractComponentTree(
|
||||
renderer: any,
|
||||
fiber: Fiber,
|
||||
): ComponentTreeNode | null {
|
||||
const result = generateComponentTree(renderer, fiber, false);
|
||||
|
||||
const idToDevToolsInstanceMap = (result as any).idToDevToolsInstanceMap;
|
||||
const currentRoot = (result as any).currentRoot;
|
||||
|
||||
if (!currentRoot || !idToDevToolsInstanceMap) {
|
||||
console.error(
|
||||
'Failed to extract component tree - internal structures not available',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return convertDevToolsInstanceToTreeNode(currentRoot);
|
||||
|
||||
/**
|
||||
* Helper function to convert a DevTools instance to our tree node format
|
||||
*/
|
||||
function convertDevToolsInstanceToTreeNode(instance: any): ComponentTreeNode {
|
||||
// Extract name based on instance kind
|
||||
let name = 'Unknown';
|
||||
let type: string | number = 'Unknown';
|
||||
let key: string | null = null;
|
||||
let props: Record<string, any> | undefined = undefined;
|
||||
let state: Record<string, any> | undefined = undefined;
|
||||
let fiberData: Fiber | undefined = undefined;
|
||||
|
||||
if (instance.kind === 0) {
|
||||
// FIBER_INSTANCE
|
||||
const fiber = instance.data;
|
||||
fiberData = fiber;
|
||||
|
||||
// @ts-ignore - Accessing private function
|
||||
const getDisplayNameForFiber = (result as any).getDisplayNameForFiber;
|
||||
if (typeof getDisplayNameForFiber === 'function') {
|
||||
name = getDisplayNameForFiber(fiber) || 'Unknown';
|
||||
} else {
|
||||
// Fallback to type name if available
|
||||
name =
|
||||
fiber.type?.displayName ||
|
||||
fiber.type?.name ||
|
||||
(typeof fiber.type === 'string' ? fiber.type : 'Unknown');
|
||||
}
|
||||
|
||||
// Get element type
|
||||
// @ts-ignore - Accessing private function
|
||||
const getElementTypeForFiber = (result as any).getElementTypeForFiber;
|
||||
if (typeof getElementTypeForFiber === 'function') {
|
||||
type = getElementTypeForFiber(fiber);
|
||||
}
|
||||
|
||||
// Get key
|
||||
key = fiber.key !== null ? String(fiber.key) : null;
|
||||
|
||||
// Get props and state if available
|
||||
if (fiber.memoizedProps) {
|
||||
props = {...fiber.memoizedProps};
|
||||
}
|
||||
|
||||
if (fiber.memoizedState) {
|
||||
// For hooks, this might be complex, so we'll just indicate it exists
|
||||
state =
|
||||
typeof fiber.memoizedState === 'object'
|
||||
? {...fiber.memoizedState}
|
||||
: {value: fiber.memoizedState};
|
||||
}
|
||||
} else if (instance.kind === 1) {
|
||||
// VIRTUAL_INSTANCE
|
||||
name = instance.data.name || 'Virtual';
|
||||
type = 'Virtual';
|
||||
key = instance.data.key != null ? String(instance.data.key) : null;
|
||||
|
||||
// Add environment if it exists
|
||||
if (instance.data.env) {
|
||||
props = {env: instance.data.env};
|
||||
}
|
||||
} else if (instance.kind === 2) {
|
||||
// FILTERED_FIBER_INSTANCE
|
||||
name = 'Filtered';
|
||||
type = 'Filtered';
|
||||
}
|
||||
|
||||
// Create the node
|
||||
const node: ComponentTreeNode = {
|
||||
id: instance.id,
|
||||
name,
|
||||
type,
|
||||
key,
|
||||
children: [],
|
||||
props,
|
||||
state: state,
|
||||
fiber: fiberData,
|
||||
};
|
||||
|
||||
// Add children recursively
|
||||
let child = instance.firstChild;
|
||||
while (child) {
|
||||
node.children.push(convertDevToolsInstanceToTreeNode(child));
|
||||
child = child.nextSibling;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the component tree as a string
|
||||
* @param tree The component tree to format
|
||||
* @param options Options for formatting
|
||||
* @returns A formatted string representation of the component tree
|
||||
*/
|
||||
export function formatComponentTree(
|
||||
tree: ComponentTreeNode | null,
|
||||
options: {
|
||||
maxDepth?: number;
|
||||
showIds?: boolean;
|
||||
showProps?: boolean;
|
||||
showState?: boolean;
|
||||
} = {},
|
||||
): string {
|
||||
if (!tree) {
|
||||
return 'No component tree available';
|
||||
}
|
||||
|
||||
const {
|
||||
maxDepth = Infinity,
|
||||
showIds = true,
|
||||
showProps = false,
|
||||
showState = false,
|
||||
} = options;
|
||||
|
||||
const lines: string[] = ['React Component Tree:'];
|
||||
|
||||
function formatNode(node: ComponentTreeNode, depth: number = 0) {
|
||||
if (depth > maxDepth) return;
|
||||
|
||||
const indent = ' '.repeat(depth);
|
||||
let output = `${indent}${node.name}`;
|
||||
|
||||
if (node.key) {
|
||||
output += ` key="${node.key}"`;
|
||||
}
|
||||
|
||||
if (showIds) {
|
||||
output += ` (id: ${node.id})`;
|
||||
}
|
||||
|
||||
lines.push(output);
|
||||
|
||||
// Format props if requested
|
||||
if (showProps && node.props) {
|
||||
const propsStr = JSON.stringify(node.props, null, 2)
|
||||
.split('\n')
|
||||
.map(line => `${indent} ${line}`)
|
||||
.join('\n');
|
||||
lines.push(`${indent} Props: ${propsStr}`);
|
||||
}
|
||||
|
||||
// Format state if requested
|
||||
if (showState && node.state) {
|
||||
const stateStr = JSON.stringify(node.state, null, 2)
|
||||
.split('\n')
|
||||
.map(line => `${indent} ${line}`)
|
||||
.join('\n');
|
||||
lines.push(`${indent} State: ${stateStr}`);
|
||||
}
|
||||
|
||||
// Format children recursively
|
||||
for (const child of node.children) {
|
||||
formatNode(child, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
formatNode(tree);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the component tree to the console
|
||||
* @param tree The component tree to print
|
||||
* @param options Options for printing
|
||||
*/
|
||||
export function printExtractedComponentTree(
|
||||
tree: ComponentTreeNode | null,
|
||||
options: {
|
||||
maxDepth?: number;
|
||||
showIds?: boolean;
|
||||
showProps?: boolean;
|
||||
showState?: boolean;
|
||||
} = {},
|
||||
) {
|
||||
const formattedTree = formatComponentTree(tree, options);
|
||||
console.log(formattedTree);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to extract and format the component tree in one step
|
||||
* @param renderer The React renderer
|
||||
* @param fiber The root fiber
|
||||
* @param options Options for formatting
|
||||
* @returns An object containing the tree and its formatted string representation
|
||||
*/
|
||||
export function extractAndFormatComponentTree(
|
||||
renderer: any,
|
||||
fiber: Fiber,
|
||||
options: {
|
||||
maxDepth?: number;
|
||||
showIds?: boolean;
|
||||
showProps?: boolean;
|
||||
showState?: boolean;
|
||||
} = {},
|
||||
) {
|
||||
const tree = extractComponentTree(renderer, fiber);
|
||||
const formattedTree = formatComponentTree(tree, options);
|
||||
return {
|
||||
tree,
|
||||
formattedTree,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to extract and print the component tree in one step
|
||||
* @param renderer The React renderer
|
||||
* @param fiber The root fiber
|
||||
* @param options Options for printing
|
||||
* @returns The extracted component tree
|
||||
*/
|
||||
export function extractAndPrintComponentTree(
|
||||
renderer: any,
|
||||
fiber: Fiber,
|
||||
options: {
|
||||
maxDepth?: number;
|
||||
showIds?: boolean;
|
||||
showProps?: boolean;
|
||||
showState?: boolean;
|
||||
} = {},
|
||||
) {
|
||||
const {tree, formattedTree} = extractAndFormatComponentTree(
|
||||
renderer,
|
||||
fiber,
|
||||
options,
|
||||
);
|
||||
console.log(formattedTree);
|
||||
return tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to extract the component tree from the React DevTools hook
|
||||
* @param options Options for formatting
|
||||
* @returns An object containing the tree and its formatted string representation, or null if extraction fails
|
||||
*/
|
||||
export default function extractComponentTreeFromDevTools(hook: any) {
|
||||
if (!hook) {
|
||||
console.error(
|
||||
'React DevTools hook is not available. Make sure React DevTools extension is installed.',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const renderers: any = Array.from(hook.renderers.values());
|
||||
if (renderers.length === 0) {
|
||||
console.error('No React renderers found.');
|
||||
return null;
|
||||
}
|
||||
|
||||
const fiberRoots: any = Array.from(
|
||||
hook.getFiberRoots(renderers[0].rendererID),
|
||||
);
|
||||
if (fiberRoots.length === 0) {
|
||||
console.error('No fiber roots found.');
|
||||
return null;
|
||||
}
|
||||
|
||||
const currentFiber = fiberRoots[0].current;
|
||||
|
||||
const options: any = {
|
||||
maxDepth: 3,
|
||||
showIds: true,
|
||||
showProps: true,
|
||||
showState: true,
|
||||
};
|
||||
|
||||
return extractAndFormatComponentTree(renderers[0], currentFiber, options);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Flip this flag to true to enable verbose console debug logging.
|
||||
export const __DEBUG__ = false;
|
||||
|
||||
// Tree operation constants
|
||||
export const TREE_OPERATION_ADD = 1;
|
||||
export const TREE_OPERATION_REMOVE = 2;
|
||||
export const TREE_OPERATION_REORDER_CHILDREN = 3;
|
||||
export const TREE_OPERATION_UPDATE_TREE_BASE_DURATION = 4;
|
||||
export const TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS = 5;
|
||||
export const TREE_OPERATION_REMOVE_ROOT = 6;
|
||||
export const TREE_OPERATION_SET_SUBTREE_MODE = 7;
|
||||
|
||||
// Profiling constants
|
||||
export const PROFILING_FLAG_BASIC_SUPPORT = 0b01;
|
||||
export const PROFILING_FLAG_TIMELINE_SUPPORT = 0b10;
|
||||
@@ -0,0 +1,32 @@
|
||||
export const MEMO_NUMBER = 0xead3;
|
||||
export const MEMO_SYMBOL_STRING = 'Symbol(react.memo)';
|
||||
export const FORWARD_REF_NUMBER = 0xead0;
|
||||
export const FORWARD_REF_SYMBOL_STRING = 'Symbol(react.forward_ref)';
|
||||
|
||||
export const REACT_MEMO_CACHE_SENTINEL: symbol = Symbol.for(
|
||||
'react.memo_cache_sentinel',
|
||||
);
|
||||
|
||||
export const CONCURRENT_MODE_NUMBER = 0xeacf;
|
||||
export const CONCURRENT_MODE_SYMBOL_STRING = 'Symbol(react.concurrent_mode)';
|
||||
|
||||
export const CONTEXT_NUMBER = 0xeace;
|
||||
export const CONTEXT_SYMBOL_STRING = 'Symbol(react.context)';
|
||||
|
||||
export const SERVER_CONTEXT_SYMBOL_STRING = 'Symbol(react.server_context)';
|
||||
|
||||
export const DEPRECATED_ASYNC_MODE_SYMBOL_STRING = 'Symbol(react.async_mode)';
|
||||
|
||||
export const PROVIDER_NUMBER = 0xeacd;
|
||||
export const PROVIDER_SYMBOL_STRING = 'Symbol(react.provider)';
|
||||
|
||||
export const CONSUMER_SYMBOL_STRING = 'Symbol(react.consumer)';
|
||||
|
||||
export const STRICT_MODE_NUMBER = 0xeacc;
|
||||
export const STRICT_MODE_SYMBOL_STRING = 'Symbol(react.strict_mode)';
|
||||
|
||||
export const PROFILER_NUMBER = 0xead2;
|
||||
export const PROFILER_SYMBOL_STRING = 'Symbol(react.profiler)';
|
||||
|
||||
export const SCOPE_NUMBER = 0xead7;
|
||||
export const SCOPE_SYMBOL_STRING = 'Symbol(react.scope)';
|
||||
@@ -0,0 +1,305 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Types migrated from shared/ReactTypes.js
|
||||
export type ReactCallSite = [
|
||||
string, // function name
|
||||
string, // file name
|
||||
number, // line number
|
||||
number, // column number
|
||||
];
|
||||
|
||||
export type ReactStackTrace = Array<ReactCallSite>;
|
||||
|
||||
export type ReactComponentInfo = {
|
||||
name: string;
|
||||
env?: string;
|
||||
key?: null | string;
|
||||
owner?: null | ReactComponentInfo;
|
||||
stack?: null | ReactStackTrace;
|
||||
props?: null | {[name: string]: any};
|
||||
// Stashed Data for the Specific Execution Environment. Not part of the transport protocol
|
||||
debugStack?: null | Error;
|
||||
debugTask?: null | any;
|
||||
};
|
||||
|
||||
export type ReactDebugInfo = Array<
|
||||
ReactComponentInfo | any // Simplified from the original which included other types
|
||||
>;
|
||||
|
||||
// Types migrated from react-reconciler/src/ReactInternalTypes.js
|
||||
export type WorkTag = number;
|
||||
export type Lanes = number;
|
||||
export type Lane = number;
|
||||
export type TypeOfMode = number;
|
||||
export type Flags = number;
|
||||
|
||||
// Migrated from react-devtools-shared/src/backend/types.js
|
||||
export type WorkTagMap = {
|
||||
CacheComponent: WorkTag;
|
||||
ClassComponent: WorkTag;
|
||||
ContextConsumer: WorkTag;
|
||||
ContextProvider: WorkTag;
|
||||
CoroutineComponent: WorkTag;
|
||||
CoroutineHandlerPhase: WorkTag;
|
||||
DehydratedSuspenseComponent: WorkTag;
|
||||
ForwardRef: WorkTag;
|
||||
Fragment: WorkTag;
|
||||
FunctionComponent: WorkTag;
|
||||
HostComponent: WorkTag;
|
||||
HostPortal: WorkTag;
|
||||
HostRoot: WorkTag;
|
||||
HostHoistable: WorkTag;
|
||||
HostSingleton: WorkTag;
|
||||
HostText: WorkTag;
|
||||
IncompleteClassComponent: WorkTag;
|
||||
IncompleteFunctionComponent: WorkTag;
|
||||
IndeterminateComponent: WorkTag;
|
||||
LazyComponent: WorkTag;
|
||||
LegacyHiddenComponent: WorkTag;
|
||||
MemoComponent: WorkTag;
|
||||
Mode: WorkTag;
|
||||
OffscreenComponent: WorkTag;
|
||||
Profiler: WorkTag;
|
||||
ScopeComponent: WorkTag;
|
||||
SimpleMemoComponent: WorkTag;
|
||||
SuspenseComponent: WorkTag;
|
||||
SuspenseListComponent: WorkTag;
|
||||
TracingMarkerComponent: WorkTag;
|
||||
YieldComponent: WorkTag;
|
||||
Throw: WorkTag;
|
||||
ViewTransitionComponent: WorkTag;
|
||||
ActivityComponent: WorkTag;
|
||||
};
|
||||
|
||||
// Dependencies for Fiber
|
||||
export type Dependencies = {
|
||||
lanes: Lanes;
|
||||
firstContext: any | null;
|
||||
};
|
||||
|
||||
// A simplified version of the Fiber type from react-reconciler/src/ReactInternalTypes.js
|
||||
export type Fiber = {
|
||||
// Tag identifying the type of fiber.
|
||||
tag: WorkTag;
|
||||
// Unique identifier of this child.
|
||||
key: null | string;
|
||||
// The value of element.type which is used to preserve the identity during
|
||||
// reconciliation of this child.
|
||||
elementType: any;
|
||||
// The resolved function/class/ associated with this fiber.
|
||||
type: any;
|
||||
// The local state associated with this fiber.
|
||||
stateNode: any;
|
||||
// Remaining fields belong to Fiber
|
||||
// The Fiber to return to after finishing processing this one.
|
||||
return: Fiber | null;
|
||||
// Singly Linked List Tree Structure.
|
||||
child: Fiber | null;
|
||||
sibling: Fiber | null;
|
||||
index: number;
|
||||
// The ref last used to attach this node.
|
||||
ref: any;
|
||||
refCleanup: null | (() => void);
|
||||
// Input is the data coming into process this fiber. Arguments. Props.
|
||||
pendingProps: any;
|
||||
memoizedProps: any;
|
||||
// A queue of state updates and callbacks.
|
||||
updateQueue: any;
|
||||
// The state used to create the output
|
||||
memoizedState: any;
|
||||
// Dependencies (contexts, events) for this fiber, if it has any
|
||||
dependencies: Dependencies | null;
|
||||
// Bitfield that describes properties about the fiber and its subtree.
|
||||
mode: TypeOfMode;
|
||||
// Effect
|
||||
flags: Flags;
|
||||
subtreeFlags: Flags;
|
||||
deletions: Array<Fiber> | null;
|
||||
lanes: Lanes;
|
||||
childLanes: Lanes;
|
||||
// This is a pooled version of a Fiber. Every fiber that gets updated will
|
||||
// eventually have a pair. There are cases when we can clean up pairs to save
|
||||
// memory if we need to.
|
||||
alternate: Fiber | null;
|
||||
// Time spent rendering this Fiber and its descendants for the current update.
|
||||
actualDuration?: number;
|
||||
// If the Fiber is currently active in the "render" phase,
|
||||
// This marks the time at which the work began.
|
||||
actualStartTime?: number;
|
||||
// Duration of the most recent render time for this Fiber.
|
||||
selfBaseDuration?: number;
|
||||
// Sum of base times for all descendants of this Fiber.
|
||||
treeBaseDuration?: number;
|
||||
// DEV only fields
|
||||
_debugInfo?: ReactDebugInfo | null;
|
||||
_debugOwner?: ReactComponentInfo | Fiber | null;
|
||||
_debugStack?: string | Error | null;
|
||||
_debugTask?: any | null;
|
||||
_debugNeedsRemount?: boolean;
|
||||
_debugHookTypes?: Array<string> | null;
|
||||
};
|
||||
|
||||
// A simplified version of the FiberRoot type from react-reconciler/src/ReactInternalTypes.js
|
||||
export type FiberRoot = {
|
||||
// The type of root (legacy, batched, concurrent, etc.)
|
||||
tag: number;
|
||||
// Any additional information from the host associated with this root.
|
||||
containerInfo: any;
|
||||
// Used only by persistent updates.
|
||||
pendingChildren: any;
|
||||
// The currently active root fiber. This is the mutable root of the tree.
|
||||
current: Fiber;
|
||||
// A linked list of all roots that have pending work scheduled on them.
|
||||
next: FiberRoot | null;
|
||||
// Other fields omitted for simplicity
|
||||
};
|
||||
|
||||
// Types migrated from react-devtools-shared/src/backend/types.js
|
||||
export type BundleType = 0 | 1; // 0 = PROD, 1 = DEV
|
||||
|
||||
export type HostInstance = any;
|
||||
|
||||
export type Source = {
|
||||
fileName: string;
|
||||
lineNumber: number;
|
||||
columnNumber?: number;
|
||||
};
|
||||
|
||||
export type ReactRenderer = {
|
||||
version: string;
|
||||
rendererPackageName: string;
|
||||
bundleType: BundleType;
|
||||
// 16.0+ - To be removed in future versions.
|
||||
findFiberByHostInstance?: (hostInstance: HostInstance) => any | null;
|
||||
// 16.9+
|
||||
overrideHookState?: (
|
||||
fiber: any,
|
||||
id: number,
|
||||
path: Array<string | number>,
|
||||
value: any,
|
||||
) => void;
|
||||
// 17+
|
||||
overrideHookStateDeletePath?: (
|
||||
fiber: any,
|
||||
id: number,
|
||||
path: Array<string | number>,
|
||||
) => void;
|
||||
// 17+
|
||||
overrideHookStateRenamePath?: (
|
||||
fiber: any,
|
||||
id: number,
|
||||
oldPath: Array<string | number>,
|
||||
newPath: Array<string | number>,
|
||||
) => void;
|
||||
// 16.7+
|
||||
overrideProps?: (
|
||||
fiber: any,
|
||||
path: Array<string | number>,
|
||||
value: any,
|
||||
) => void;
|
||||
// 17+
|
||||
overridePropsDeletePath?: (fiber: any, path: Array<string | number>) => void;
|
||||
// 17+
|
||||
overridePropsRenamePath?: (
|
||||
fiber: any,
|
||||
oldPath: Array<string | number>,
|
||||
newPath: Array<string | number>,
|
||||
) => void;
|
||||
// 16.9+
|
||||
scheduleUpdate?: (fiber: any) => void;
|
||||
setSuspenseHandler?: (shouldSuspend: (fiber: any) => boolean) => void;
|
||||
// Only injected by React v16.8+ in order to support hooks inspection.
|
||||
currentDispatcherRef?: any;
|
||||
// Only injected by React v16.9+ in DEV mode.
|
||||
// Enables DevTools to append owners-only component stack to error messages.
|
||||
getCurrentFiber?: (() => any | null) | null;
|
||||
// Only injected by React Flight Clients in DEV mode.
|
||||
// Enables DevTools to append owners-only component stack to error messages from Server Components.
|
||||
getCurrentComponentInfo?: () => any | null;
|
||||
// 17.0.2+
|
||||
reconcilerVersion?: string;
|
||||
// Uniquely identifies React DOM v15.
|
||||
ComponentTree?: any;
|
||||
// Present for React DOM v12 (possibly earlier) through v15.
|
||||
Mount?: any;
|
||||
// Only injected by React v17.0.3+ in DEV mode
|
||||
setErrorHandler?: (shouldError: (fiber: any) => boolean | undefined) => void;
|
||||
// Intentionally opaque type to avoid coupling DevTools to different Fast Refresh versions.
|
||||
scheduleRefresh?: Function;
|
||||
// 18.0+
|
||||
injectProfilingHooks?: (profilingHooks: any) => void;
|
||||
getLaneLabelMap?: () => Map<number, string> | null;
|
||||
};
|
||||
|
||||
export type ChangeDescription = {
|
||||
context: Array<string> | boolean | null;
|
||||
didHooksChange: boolean;
|
||||
isFirstMount: boolean;
|
||||
props: Array<string> | null;
|
||||
state: Array<string> | null;
|
||||
hooks?: Array<number> | null;
|
||||
};
|
||||
|
||||
export type PathFrame = {
|
||||
key: string | null;
|
||||
index: number;
|
||||
displayName: string | null;
|
||||
};
|
||||
|
||||
export type SerializedElement = {
|
||||
displayName: string | null;
|
||||
id: number;
|
||||
key: number | string | null;
|
||||
type: ElementType;
|
||||
};
|
||||
|
||||
// WARNING
|
||||
// The values below are referenced by ComponentFilters (which are saved via localStorage).
|
||||
// Do not change them or it will break previously saved user customizations.
|
||||
// If new element types are added, use new numbers rather than re-ordering existing ones.
|
||||
//
|
||||
// Changing these types is also a backwards breaking change for the standalone shell,
|
||||
// since the frontend and backend must share the same values-
|
||||
// and the backend is embedded in certain environments (like React Native).
|
||||
export const ElementTypeClass = 1;
|
||||
export const ElementTypeContext = 2;
|
||||
export const ElementTypeFunction = 5;
|
||||
export const ElementTypeForwardRef = 6;
|
||||
export const ElementTypeHostComponent = 7;
|
||||
export const ElementTypeMemo = 8;
|
||||
export const ElementTypeOtherOrUnknown = 9;
|
||||
export const ElementTypeProfiler = 10;
|
||||
export const ElementTypeRoot = 11;
|
||||
export const ElementTypeSuspense = 12;
|
||||
export const ElementTypeSuspenseList = 13;
|
||||
export const ElementTypeTracingMarker = 14;
|
||||
export const ElementTypeVirtual = 15;
|
||||
export const ElementTypeViewTransition = 16;
|
||||
export const ElementTypeActivity = 17;
|
||||
|
||||
// Different types of elements displayed in the Elements tree.
|
||||
// These types may be used to visually distinguish types,
|
||||
// or to enable/disable certain functionality.
|
||||
export type ElementType =
|
||||
| 1
|
||||
| 2
|
||||
| 5
|
||||
| 6
|
||||
| 7
|
||||
| 8
|
||||
| 9
|
||||
| 10
|
||||
| 11
|
||||
| 12
|
||||
| 13
|
||||
| 14
|
||||
| 15
|
||||
| 16
|
||||
| 17;
|
||||
|
||||
export const StrictMode = 1;
|
||||
@@ -0,0 +1,608 @@
|
||||
import {WorkTagMap} from './reactDevToolsTypes';
|
||||
import {compareVersions} from 'compare-versions';
|
||||
import {ReactComponentInfo} from './reactDevToolsTypes';
|
||||
|
||||
// Migrated from react-devtools-shared/src/backend/utils/index.js
|
||||
export function gt(a: string = '', b: string = ''): boolean {
|
||||
return compareVersions(a, b) === 1;
|
||||
}
|
||||
|
||||
export function gte(a: string = '', b: string = ''): boolean {
|
||||
return compareVersions(a, b) > -1;
|
||||
}
|
||||
|
||||
// Migrated from react-devtools-shared/src/utils.js
|
||||
const cachedDisplayNames: WeakMap<Function, string> = new WeakMap();
|
||||
|
||||
export function getWrappedDisplayName(
|
||||
outerType: any,
|
||||
innerType: any,
|
||||
wrapperName: string,
|
||||
fallbackName: string = 'Anonymous',
|
||||
): string {
|
||||
const displayName = outerType?.displayName;
|
||||
return (
|
||||
displayName || `${wrapperName}(${getDisplayName(innerType, fallbackName)})`
|
||||
);
|
||||
}
|
||||
|
||||
export function getDisplayName(
|
||||
type: any,
|
||||
fallbackName: string = 'Anonymous',
|
||||
): string {
|
||||
const nameFromCache = cachedDisplayNames.get(type);
|
||||
if (nameFromCache != null) {
|
||||
return nameFromCache;
|
||||
}
|
||||
|
||||
let displayName = fallbackName;
|
||||
|
||||
// The displayName property is not guaranteed to be a string.
|
||||
// It's only safe to use for our purposes if it's a string.
|
||||
if (typeof type.displayName === 'string') {
|
||||
displayName = type.displayName;
|
||||
} else if (typeof type.name === 'string' && type.name !== '') {
|
||||
displayName = type.name;
|
||||
}
|
||||
|
||||
cachedDisplayNames.set(type, displayName);
|
||||
return displayName;
|
||||
}
|
||||
|
||||
// Migrated from react-devtools-shared/src/backend/shared/DevToolsServerComponentLogs.js
|
||||
type ComponentLogs = {
|
||||
errors: Map<string, number>;
|
||||
errorsCount: number;
|
||||
warnings: Map<string, number>;
|
||||
warningsCount: number;
|
||||
};
|
||||
|
||||
// This keeps it around as long as the ComponentInfo is alive which
|
||||
// lets the Fiber get reparented/remounted and still observe the previous errors/warnings.
|
||||
// Unless we explicitly clear the logs from a Fiber.
|
||||
export const componentInfoToComponentLogsMap: WeakMap<
|
||||
ReactComponentInfo,
|
||||
ComponentLogs
|
||||
> = new WeakMap();
|
||||
|
||||
// Migrated from shared/objectIs.js
|
||||
/**
|
||||
* inlined Object.is polyfill to avoid requiring consumers ship their own
|
||||
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
|
||||
*/
|
||||
function isPolyfill(x: any, y: any) {
|
||||
return (
|
||||
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
|
||||
);
|
||||
}
|
||||
|
||||
export const is: (x: any, y: any) => boolean =
|
||||
typeof Object.is === 'function' ? Object.is : isPolyfill;
|
||||
|
||||
// Migrated from shared/hasOwnProperty.js
|
||||
export const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
import {
|
||||
MEMO_NUMBER,
|
||||
MEMO_SYMBOL_STRING,
|
||||
FORWARD_REF_NUMBER,
|
||||
FORWARD_REF_SYMBOL_STRING,
|
||||
REACT_MEMO_CACHE_SENTINEL,
|
||||
CONCURRENT_MODE_NUMBER,
|
||||
CONCURRENT_MODE_SYMBOL_STRING,
|
||||
DEPRECATED_ASYNC_MODE_SYMBOL_STRING,
|
||||
PROVIDER_NUMBER,
|
||||
PROVIDER_SYMBOL_STRING,
|
||||
CONTEXT_NUMBER,
|
||||
CONTEXT_SYMBOL_STRING,
|
||||
SERVER_CONTEXT_SYMBOL_STRING,
|
||||
CONSUMER_SYMBOL_STRING,
|
||||
STRICT_MODE_NUMBER,
|
||||
STRICT_MODE_SYMBOL_STRING,
|
||||
PROFILER_NUMBER,
|
||||
PROFILER_SYMBOL_STRING,
|
||||
SCOPE_NUMBER,
|
||||
SCOPE_SYMBOL_STRING,
|
||||
} from './reactDevToolsSymbols';
|
||||
import {Fiber} from './reactDevToolsTypes';
|
||||
|
||||
// Migrated from react-devtools-shared/src/utils.js
|
||||
let uidCounter: number = 0;
|
||||
|
||||
export function getUID(): number {
|
||||
return ++uidCounter;
|
||||
}
|
||||
|
||||
export function utfEncodeString(string: string): Array<number> {
|
||||
const encoded = [];
|
||||
let i = 0;
|
||||
let charCode;
|
||||
while (i < string.length) {
|
||||
charCode = string.charCodeAt(i);
|
||||
// Handle multibyte unicode characters (like emoji).
|
||||
if ((charCode & 0xf800) === 0xd800) {
|
||||
encoded.push(surrogatePairToCodePoint(charCode, string.charCodeAt(++i)));
|
||||
} else {
|
||||
encoded.push(charCode);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
|
||||
function surrogatePairToCodePoint(
|
||||
charCode1: number,
|
||||
charCode2: number,
|
||||
): number {
|
||||
return ((charCode1 & 0x3ff) << 10) + (charCode2 & 0x3ff) + 0x10000;
|
||||
}
|
||||
|
||||
// Migrated from react-reconciler/src/ReactFiberDevToolsHook.js
|
||||
export function injectProfilingHooks(profilingHooks: any): void {
|
||||
// This is a simplified version of the function from ReactFiberDevToolsHook.js
|
||||
// We're only implementing the bare minimum needed for the current use case
|
||||
}
|
||||
|
||||
type getDisplayNameForFiberType = (fiber: Fiber) => string | null;
|
||||
type getTypeSymbolType = (type: any) => symbol | string | number;
|
||||
|
||||
type ReactPriorityLevelsType = {
|
||||
ImmediatePriority: number;
|
||||
UserBlockingPriority: number;
|
||||
NormalPriority: number;
|
||||
LowPriority: number;
|
||||
IdlePriority: number;
|
||||
NoPriority: number;
|
||||
};
|
||||
|
||||
export function getInternalReactConstants(version: string): {
|
||||
getDisplayNameForFiber: getDisplayNameForFiberType;
|
||||
getTypeSymbol: getTypeSymbolType;
|
||||
ReactPriorityLevels: ReactPriorityLevelsType;
|
||||
ReactTypeOfWork: WorkTagMap;
|
||||
StrictModeBits: number;
|
||||
} {
|
||||
// **********************************************************
|
||||
// The section below is copied from files in React repo.
|
||||
// Keep it in sync, and add version guards if it changes.
|
||||
//
|
||||
// Technically these priority levels are invalid for versions before 16.9,
|
||||
// but 16.9 is the first version to report priority level to DevTools,
|
||||
// so we can avoid checking for earlier versions and support pre-16.9 canary releases in the process.
|
||||
let ReactPriorityLevels: ReactPriorityLevelsType = {
|
||||
ImmediatePriority: 99,
|
||||
UserBlockingPriority: 98,
|
||||
NormalPriority: 97,
|
||||
LowPriority: 96,
|
||||
IdlePriority: 95,
|
||||
NoPriority: 90,
|
||||
};
|
||||
|
||||
if (gt(version, '17.0.2')) {
|
||||
ReactPriorityLevels = {
|
||||
ImmediatePriority: 1,
|
||||
UserBlockingPriority: 2,
|
||||
NormalPriority: 3,
|
||||
LowPriority: 4,
|
||||
IdlePriority: 5,
|
||||
NoPriority: 0,
|
||||
};
|
||||
}
|
||||
|
||||
let StrictModeBits = 0;
|
||||
if (gte(version, '18.0.0-alpha')) {
|
||||
// 18+
|
||||
StrictModeBits = 0b011000;
|
||||
} else if (gte(version, '16.9.0')) {
|
||||
// 16.9 - 17
|
||||
StrictModeBits = 0b1;
|
||||
} else if (gte(version, '16.3.0')) {
|
||||
// 16.3 - 16.8
|
||||
StrictModeBits = 0b10;
|
||||
}
|
||||
|
||||
let ReactTypeOfWork: WorkTagMap = {} as WorkTagMap;
|
||||
|
||||
// **********************************************************
|
||||
// The section below is copied from files in React repo.
|
||||
// Keep it in sync, and add version guards if it changes.
|
||||
//
|
||||
// TODO Update the gt() check below to be gte() whichever the next version number is.
|
||||
// Currently the version in Git is 17.0.2 (but that version has not been/may not end up being released).
|
||||
if (gt(version, '17.0.1')) {
|
||||
ReactTypeOfWork = {
|
||||
CacheComponent: 24, // Experimental
|
||||
ClassComponent: 1,
|
||||
ContextConsumer: 9,
|
||||
ContextProvider: 10,
|
||||
CoroutineComponent: -1, // Removed
|
||||
CoroutineHandlerPhase: -1, // Removed
|
||||
DehydratedSuspenseComponent: 18, // Behind a flag
|
||||
ForwardRef: 11,
|
||||
Fragment: 7,
|
||||
FunctionComponent: 0,
|
||||
HostComponent: 5,
|
||||
HostPortal: 4,
|
||||
HostRoot: 3,
|
||||
HostHoistable: 26, // In reality, 18.2+. But doesn't hurt to include it here
|
||||
HostSingleton: 27, // Same as above
|
||||
HostText: 6,
|
||||
IncompleteClassComponent: 17,
|
||||
IncompleteFunctionComponent: 28,
|
||||
IndeterminateComponent: 2, // removed in 19.0.0
|
||||
LazyComponent: 16,
|
||||
LegacyHiddenComponent: 23,
|
||||
MemoComponent: 14,
|
||||
Mode: 8,
|
||||
OffscreenComponent: 22, // Experimental
|
||||
Profiler: 12,
|
||||
ScopeComponent: 21, // Experimental
|
||||
SimpleMemoComponent: 15,
|
||||
SuspenseComponent: 13,
|
||||
SuspenseListComponent: 19, // Experimental
|
||||
TracingMarkerComponent: 25, // Experimental - This is technically in 18 but we don't
|
||||
// want to fork again so we're adding it here instead
|
||||
YieldComponent: -1, // Removed
|
||||
Throw: 29,
|
||||
ViewTransitionComponent: 30, // Experimental
|
||||
ActivityComponent: 31,
|
||||
};
|
||||
} else if (gte(version, '17.0.0-alpha')) {
|
||||
ReactTypeOfWork = {
|
||||
CacheComponent: -1, // Doesn't exist yet
|
||||
ClassComponent: 1,
|
||||
ContextConsumer: 9,
|
||||
ContextProvider: 10,
|
||||
CoroutineComponent: -1, // Removed
|
||||
CoroutineHandlerPhase: -1, // Removed
|
||||
DehydratedSuspenseComponent: 18, // Behind a flag
|
||||
ForwardRef: 11,
|
||||
Fragment: 7,
|
||||
FunctionComponent: 0,
|
||||
HostComponent: 5,
|
||||
HostPortal: 4,
|
||||
HostRoot: 3,
|
||||
HostHoistable: -1, // Doesn't exist yet
|
||||
HostSingleton: -1, // Doesn't exist yet
|
||||
HostText: 6,
|
||||
IncompleteClassComponent: 17,
|
||||
IncompleteFunctionComponent: -1, // Doesn't exist yet
|
||||
IndeterminateComponent: 2,
|
||||
LazyComponent: 16,
|
||||
LegacyHiddenComponent: 24,
|
||||
MemoComponent: 14,
|
||||
Mode: 8,
|
||||
OffscreenComponent: 23, // Experimental
|
||||
Profiler: 12,
|
||||
ScopeComponent: 21, // Experimental
|
||||
SimpleMemoComponent: 15,
|
||||
SuspenseComponent: 13,
|
||||
SuspenseListComponent: 19, // Experimental
|
||||
TracingMarkerComponent: -1, // Doesn't exist yet
|
||||
YieldComponent: -1, // Removed
|
||||
Throw: -1, // Doesn't exist yet
|
||||
ViewTransitionComponent: -1, // Doesn't exist yet
|
||||
ActivityComponent: -1, // Doesn't exist yet
|
||||
};
|
||||
} else if (gte(version, '16.6.0-beta.0')) {
|
||||
ReactTypeOfWork = {
|
||||
CacheComponent: -1, // Doesn't exist yet
|
||||
ClassComponent: 1,
|
||||
ContextConsumer: 9,
|
||||
ContextProvider: 10,
|
||||
CoroutineComponent: -1, // Removed
|
||||
CoroutineHandlerPhase: -1, // Removed
|
||||
DehydratedSuspenseComponent: 18, // Behind a flag
|
||||
ForwardRef: 11,
|
||||
Fragment: 7,
|
||||
FunctionComponent: 0,
|
||||
HostComponent: 5,
|
||||
HostPortal: 4,
|
||||
HostRoot: 3,
|
||||
HostHoistable: -1, // Doesn't exist yet
|
||||
HostSingleton: -1, // Doesn't exist yet
|
||||
HostText: 6,
|
||||
IncompleteClassComponent: 17,
|
||||
IncompleteFunctionComponent: -1, // Doesn't exist yet
|
||||
IndeterminateComponent: 2,
|
||||
LazyComponent: 16,
|
||||
LegacyHiddenComponent: -1,
|
||||
MemoComponent: 14,
|
||||
Mode: 8,
|
||||
OffscreenComponent: -1, // Experimental
|
||||
Profiler: 12,
|
||||
ScopeComponent: -1, // Experimental
|
||||
SimpleMemoComponent: 15,
|
||||
SuspenseComponent: 13,
|
||||
SuspenseListComponent: 19, // Experimental
|
||||
TracingMarkerComponent: -1, // Doesn't exist yet
|
||||
YieldComponent: -1, // Removed
|
||||
Throw: -1, // Doesn't exist yet
|
||||
ViewTransitionComponent: -1, // Doesn't exist yet
|
||||
ActivityComponent: -1, // Doesn't exist yet
|
||||
};
|
||||
} else if (gte(version, '16.4.3-alpha')) {
|
||||
ReactTypeOfWork = {
|
||||
CacheComponent: -1, // Doesn't exist yet
|
||||
ClassComponent: 2,
|
||||
ContextConsumer: 11,
|
||||
ContextProvider: 12,
|
||||
CoroutineComponent: -1, // Removed
|
||||
CoroutineHandlerPhase: -1, // Removed
|
||||
DehydratedSuspenseComponent: -1, // Doesn't exist yet
|
||||
ForwardRef: 13,
|
||||
Fragment: 9,
|
||||
FunctionComponent: 0,
|
||||
HostComponent: 7,
|
||||
HostPortal: 6,
|
||||
HostRoot: 5,
|
||||
HostHoistable: -1, // Doesn't exist yet
|
||||
HostSingleton: -1, // Doesn't exist yet
|
||||
HostText: 8,
|
||||
IncompleteClassComponent: -1, // Doesn't exist yet
|
||||
IncompleteFunctionComponent: -1, // Doesn't exist yet
|
||||
IndeterminateComponent: 4,
|
||||
LazyComponent: -1, // Doesn't exist yet
|
||||
LegacyHiddenComponent: -1,
|
||||
MemoComponent: -1, // Doesn't exist yet
|
||||
Mode: 10,
|
||||
OffscreenComponent: -1, // Experimental
|
||||
Profiler: 15,
|
||||
ScopeComponent: -1, // Experimental
|
||||
SimpleMemoComponent: -1, // Doesn't exist yet
|
||||
SuspenseComponent: 16,
|
||||
SuspenseListComponent: -1, // Doesn't exist yet
|
||||
TracingMarkerComponent: -1, // Doesn't exist yet
|
||||
YieldComponent: -1, // Removed
|
||||
Throw: -1, // Doesn't exist yet
|
||||
ViewTransitionComponent: -1, // Doesn't exist yet
|
||||
ActivityComponent: -1, // Doesn't exist yet
|
||||
};
|
||||
} else {
|
||||
ReactTypeOfWork = {
|
||||
CacheComponent: -1, // Doesn't exist yet
|
||||
ClassComponent: 2,
|
||||
ContextConsumer: 12,
|
||||
ContextProvider: 13,
|
||||
CoroutineComponent: 7,
|
||||
CoroutineHandlerPhase: 8,
|
||||
DehydratedSuspenseComponent: -1, // Doesn't exist yet
|
||||
ForwardRef: 14,
|
||||
Fragment: 10,
|
||||
FunctionComponent: 1,
|
||||
HostComponent: 5,
|
||||
HostPortal: 4,
|
||||
HostRoot: 3,
|
||||
HostHoistable: -1, // Doesn't exist yet
|
||||
HostSingleton: -1, // Doesn't exist yet
|
||||
HostText: 6,
|
||||
IncompleteClassComponent: -1, // Doesn't exist yet
|
||||
IncompleteFunctionComponent: -1, // Doesn't exist yet
|
||||
IndeterminateComponent: 0,
|
||||
LazyComponent: -1, // Doesn't exist yet
|
||||
LegacyHiddenComponent: -1,
|
||||
MemoComponent: -1, // Doesn't exist yet
|
||||
Mode: 11,
|
||||
OffscreenComponent: -1, // Experimental
|
||||
Profiler: 15,
|
||||
ScopeComponent: -1, // Experimental
|
||||
SimpleMemoComponent: -1, // Doesn't exist yet
|
||||
SuspenseComponent: 16,
|
||||
SuspenseListComponent: -1, // Doesn't exist yet
|
||||
TracingMarkerComponent: -1, // Doesn't exist yet
|
||||
YieldComponent: 9,
|
||||
Throw: -1, // Doesn't exist yet
|
||||
ViewTransitionComponent: -1, // Doesn't exist yet
|
||||
ActivityComponent: -1, // Doesn't exist yet
|
||||
};
|
||||
}
|
||||
// **********************************************************
|
||||
// End of copied code.
|
||||
// **********************************************************
|
||||
|
||||
function getTypeSymbol(type: any): symbol | string | number {
|
||||
const symbolOrNumber =
|
||||
typeof type === 'object' && type !== null ? type.$$typeof : type;
|
||||
|
||||
return typeof symbolOrNumber === 'symbol'
|
||||
? symbolOrNumber.toString()
|
||||
: symbolOrNumber;
|
||||
}
|
||||
|
||||
const {
|
||||
CacheComponent,
|
||||
ClassComponent,
|
||||
IncompleteClassComponent,
|
||||
IncompleteFunctionComponent,
|
||||
FunctionComponent,
|
||||
IndeterminateComponent,
|
||||
ForwardRef,
|
||||
HostRoot,
|
||||
HostHoistable,
|
||||
HostSingleton,
|
||||
HostComponent,
|
||||
HostPortal,
|
||||
HostText,
|
||||
Fragment,
|
||||
LazyComponent,
|
||||
LegacyHiddenComponent,
|
||||
MemoComponent,
|
||||
OffscreenComponent,
|
||||
Profiler,
|
||||
ScopeComponent,
|
||||
SimpleMemoComponent,
|
||||
SuspenseComponent,
|
||||
SuspenseListComponent,
|
||||
TracingMarkerComponent,
|
||||
Throw,
|
||||
ViewTransitionComponent,
|
||||
ActivityComponent,
|
||||
} = ReactTypeOfWork;
|
||||
|
||||
// TODO: any return type might be wrong
|
||||
function resolveFiberType(type: any): any {
|
||||
const typeSymbol = getTypeSymbol(type);
|
||||
switch (typeSymbol) {
|
||||
case MEMO_NUMBER:
|
||||
case MEMO_SYMBOL_STRING:
|
||||
// recursively resolving memo type in case of memo(forwardRef(Component))
|
||||
return resolveFiberType(type.type);
|
||||
case FORWARD_REF_NUMBER:
|
||||
case FORWARD_REF_SYMBOL_STRING:
|
||||
return type.render;
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTICE Keep in sync with shouldFilterFiber() and other get*ForFiber methods
|
||||
function getDisplayNameForFiber(
|
||||
fiber: Fiber,
|
||||
shouldSkipForgetCheck: boolean = false,
|
||||
): string | null {
|
||||
const {elementType, type, tag} = fiber;
|
||||
|
||||
let resolvedType = type;
|
||||
if (typeof type === 'object' && type !== null) {
|
||||
resolvedType = resolveFiberType(type);
|
||||
}
|
||||
|
||||
let resolvedContext: any = null;
|
||||
if (
|
||||
!shouldSkipForgetCheck &&
|
||||
// $FlowFixMe[incompatible-type] fiber.updateQueue is mixed
|
||||
(fiber.updateQueue?.memoCache != null ||
|
||||
(Array.isArray(fiber.memoizedState?.memoizedState) &&
|
||||
fiber.memoizedState.memoizedState[0]?.[REACT_MEMO_CACHE_SENTINEL]) ||
|
||||
fiber.memoizedState?.memoizedState?.[REACT_MEMO_CACHE_SENTINEL])
|
||||
) {
|
||||
const displayNameWithoutForgetWrapper = getDisplayNameForFiber(
|
||||
fiber,
|
||||
true,
|
||||
);
|
||||
if (displayNameWithoutForgetWrapper == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `Forget(${displayNameWithoutForgetWrapper})`;
|
||||
}
|
||||
|
||||
switch (tag) {
|
||||
case ActivityComponent:
|
||||
return 'Activity';
|
||||
case CacheComponent:
|
||||
return 'Cache';
|
||||
case ClassComponent:
|
||||
case IncompleteClassComponent:
|
||||
case IncompleteFunctionComponent:
|
||||
case FunctionComponent:
|
||||
case IndeterminateComponent:
|
||||
return getDisplayName(resolvedType);
|
||||
case ForwardRef:
|
||||
return getWrappedDisplayName(
|
||||
elementType,
|
||||
resolvedType,
|
||||
'ForwardRef',
|
||||
'Anonymous',
|
||||
);
|
||||
case HostRoot:
|
||||
const fiberRoot = fiber.stateNode;
|
||||
if (fiberRoot != null && fiberRoot._debugRootType !== null) {
|
||||
return fiberRoot._debugRootType;
|
||||
}
|
||||
return null;
|
||||
case HostComponent:
|
||||
case HostSingleton:
|
||||
case HostHoistable:
|
||||
return type;
|
||||
case HostPortal:
|
||||
case HostText:
|
||||
return null;
|
||||
case Fragment:
|
||||
return 'Fragment';
|
||||
case LazyComponent:
|
||||
// This display name will not be user visible.
|
||||
// Once a Lazy component loads its inner component, React replaces the tag and type.
|
||||
// This display name will only show up in console logs when DevTools DEBUG mode is on.
|
||||
return 'Lazy';
|
||||
case MemoComponent:
|
||||
case SimpleMemoComponent:
|
||||
// Display name in React does not use `Memo` as a wrapper but fallback name.
|
||||
return getWrappedDisplayName(
|
||||
elementType,
|
||||
resolvedType,
|
||||
'Memo',
|
||||
'Anonymous',
|
||||
);
|
||||
case LegacyHiddenComponent:
|
||||
return 'LegacyHidden';
|
||||
case OffscreenComponent:
|
||||
return 'Offscreen';
|
||||
case ScopeComponent:
|
||||
return 'Scope';
|
||||
case SuspenseComponent:
|
||||
return 'Suspense';
|
||||
case SuspenseListComponent:
|
||||
return 'SuspenseList';
|
||||
case TracingMarkerComponent:
|
||||
return 'TracingMarker';
|
||||
case ViewTransitionComponent:
|
||||
return 'ViewTransition';
|
||||
case Throw:
|
||||
return 'Throw';
|
||||
default:
|
||||
const typeSymbol = getTypeSymbol(type);
|
||||
|
||||
switch (typeSymbol) {
|
||||
case CONCURRENT_MODE_NUMBER:
|
||||
case CONCURRENT_MODE_SYMBOL_STRING:
|
||||
case DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
|
||||
return 'ConcurrentMode';
|
||||
case PROVIDER_NUMBER:
|
||||
case PROVIDER_SYMBOL_STRING:
|
||||
// Grab the displayName from the type if it's available.
|
||||
// If it doesn't have a displayName, we'll fall back to the generic "Context.Provider" name.
|
||||
if (resolvedContext == null) {
|
||||
resolvedContext = type._context || type;
|
||||
}
|
||||
return `${resolvedContext.displayName || 'Context'}.Provider`;
|
||||
case CONTEXT_NUMBER:
|
||||
case CONTEXT_SYMBOL_STRING:
|
||||
case SERVER_CONTEXT_SYMBOL_STRING:
|
||||
// Grab the displayName from the type if it's available.
|
||||
// If it doesn't have a displayName, we'll fall back to the generic "Context.Consumer" name.
|
||||
if (resolvedContext == null) {
|
||||
resolvedContext = type._context || type;
|
||||
}
|
||||
return `${resolvedContext.displayName || 'Context'}.Consumer`;
|
||||
case CONSUMER_SYMBOL_STRING:
|
||||
// Grab the displayName from the type if it's available.
|
||||
// If it doesn't have a displayName, we'll fall back to the generic "Context.Consumer" name.
|
||||
if (resolvedContext == null) {
|
||||
resolvedContext = type;
|
||||
}
|
||||
return `${resolvedContext.displayName || 'Context'}.Consumer`;
|
||||
case STRICT_MODE_NUMBER:
|
||||
case STRICT_MODE_SYMBOL_STRING:
|
||||
return 'StrictMode';
|
||||
case PROFILER_NUMBER:
|
||||
case PROFILER_SYMBOL_STRING:
|
||||
return `Profiler`;
|
||||
case SCOPE_NUMBER:
|
||||
case SCOPE_SYMBOL_STRING:
|
||||
return 'Scope';
|
||||
default:
|
||||
// Unknown element type.
|
||||
// This may mean a new element type that has not yet been added to DevTools.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getDisplayNameForFiber,
|
||||
getTypeSymbol,
|
||||
ReactPriorityLevels,
|
||||
ReactTypeOfWork,
|
||||
StrictModeBits,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user