react-dev-inspector
Version:
dev-tool for inspect react components and jump to local IDE for component code.
176 lines (175 loc) • 7.06 kB
JavaScript
import { isNativeTagFiber, isReactSymbolFiber, isForwardRef, getDirectParentFiber, getFiberName, getElementFiberUpward, } from './fiber';
/**
* react fiber property `_debugSource` created by `@babel/plugin-transform-react-jsx-source`
* https://github.com/babel/babel/blob/v7.16.4/packages/babel-plugin-transform-react-jsx-source/src/index.js
*
* and injected `__source` property used by `React.createElement`, then pass to `ReactElement`
* https://github.com/facebook/react/blob/v18.0.0/packages/react/src/ReactElement.js#L189
* https://github.com/facebook/react/blob/v18.0.0/packages/react/src/ReactElement.js#L389
* https://github.com/facebook/react/blob/v18.0.0/packages/react/src/ReactElement.js#L447
*
* finally, used by `createFiberFromElement` to become a fiber property `_debugSource`.
* https://github.com/facebook/react/blob/v18.0.0/packages/react-reconciler/src/ReactFiber.new.js#L648-L649
*/
export const getCodeInfoFromDebugSource = (fiber) => {
var _a, _b;
if (!fiber)
return undefined;
const debugSource = ((_a = fiber._debugSource) !== null && _a !== void 0 ? _a : (_b = fiber._debugOwner) === null || _b === void 0 ? void 0 : _b._debugSource);
if (!debugSource)
return undefined;
const { fileName, lineNumber, columnNumber, } = debugSource;
if (fileName && lineNumber) {
return {
lineNumber: String(lineNumber),
columnNumber: String(columnNumber !== null && columnNumber !== void 0 ? columnNumber : 1),
/**
* `fileName` in `_debugSource` is absolutely
* ---
*
* compatible with the incorrect `fileName: "</xxx/file>"` by [rspack](https://github.com/web-infra-dev/rspack)
*/
absolutePath: fileName.match(/^<.*>$/)
? fileName.replace(/^<|>$/g, '')
: fileName,
};
}
return undefined;
};
/**
* code location data-attribute props inject by `react-dev-inspector/plugins/babel`
*/
export const getCodeInfoFromProps = (fiber) => {
if (!(fiber === null || fiber === void 0 ? void 0 : fiber.pendingProps))
return undefined;
const { 'data-inspector-line': lineNumber, 'data-inspector-column': columnNumber, 'data-inspector-relative-path': relativePath, } = fiber.pendingProps;
if (lineNumber && columnNumber && relativePath) {
return {
lineNumber,
columnNumber,
relativePath,
};
}
return undefined;
};
export const getCodeInfoFromFiber = (fiber) => {
const codeInfos = [
getCodeInfoFromDebugSource(fiber),
getCodeInfoFromProps(fiber),
].filter(Boolean);
if (!codeInfos.length)
return undefined;
return Object.assign({}, ...codeInfos);
};
/**
* give a `base` dom fiber,
* and will try to get the human friendly react component `reference` fiber from it;
*
* rules and examples see below:
* *******************************************************
*
* if parent is html native tag, `reference` is considered to be as same as `base`
*
* div div
* └─ h1 └─ h1 (<--base) <--reference
* └─ span (<--base) <--reference └─ span
*
* *******************************************************
*
* if parent is NOT html native tag,
* and parent ONLY have one child (the `base` itself),
* then `reference` is considered to be the parent.
*
* Title <--reference Title
* └─ h1 (<--base) └─ h1 (<--base) <--reference
* └─ span └─ span
* └─ div
*
* *******************************************************
*
* while follow the last one,
* "parent" is considered to skip continuous Provider/Customer/ForwardRef components
*
* Title <- reference Title <- reference
* └─ TitleName [ForwardRef] └─ TitleName [ForwardRef]
* └─ Context.Customer └─ Context.Customer
* └─ Context.Customer └─ Context.Customer
* └─ h1 (<- base) └─ h1 (<- base)
* └─ span └─ span
* └─ div
*
* Title
* └─ TitleName [ForwardRef]
* └─ Context.Customer
* └─ Context.Customer
* └─ h1 (<- base) <- reference
* └─ span
* └─ div
*/
export const getReferenceFiber = (baseFiber) => {
if (!baseFiber)
return undefined;
const directParent = getDirectParentFiber(baseFiber);
if (!directParent)
return undefined;
const isParentNative = isNativeTagFiber(directParent);
const isOnlyOneChild = !directParent.child.sibling;
let referenceFiber = (!isParentNative && isOnlyOneChild)
? directParent
: baseFiber;
// fallback for cannot find code-info fiber when traverse to root
const originReferenceFiber = referenceFiber;
while (referenceFiber) {
if (getCodeInfoFromFiber(referenceFiber))
return referenceFiber;
referenceFiber = referenceFiber.return;
}
return originReferenceFiber;
};
export const getElementCodeInfo = (element) => {
const fiber = getElementFiberUpward(element);
const referenceFiber = getReferenceFiber(fiber);
return getCodeInfoFromFiber(referenceFiber);
};
export const getNamedFiber = (baseFiber) => {
var _a, _b;
let fiber = baseFiber;
// fallback for cannot find code-info fiber when traverse to root
let originNamedFiber;
while (fiber) {
let parent = (_a = fiber.return) !== null && _a !== void 0 ? _a : undefined;
let forwardParent;
while (isReactSymbolFiber(parent)) {
if (isForwardRef(parent)) {
forwardParent = parent;
}
parent = (_b = parent === null || parent === void 0 ? void 0 : parent.return) !== null && _b !== void 0 ? _b : undefined;
}
if (forwardParent) {
fiber = forwardParent;
}
if (getFiberName(fiber)) {
if (!originNamedFiber)
originNamedFiber = fiber;
if (getCodeInfoFromFiber(fiber))
return fiber;
}
fiber = parent;
}
return originNamedFiber;
};
export const getElementInspect = (element) => {
const fiber = getElementFiberUpward(element);
const referenceFiber = getReferenceFiber(fiber);
const namedFiber = getNamedFiber(referenceFiber);
const fiberName = getFiberName(namedFiber);
const nodeName = element.nodeName.toLowerCase();
const title = fiberName
? `${nodeName} in <${fiberName}>`
: nodeName;
return {
fiber: referenceFiber,
name: fiberName,
title,
};
};