feat: show React component names in inspector tree, fix JSXMemberExpression & React 19 support#506
Conversation
…ression
## What changed
1. **JSXMemberExpression support** (`transform-jsx.ts`)
- `<Typography.H2>`, `<Page.Content>`, `<Icons.Close />` etc. now correctly
appear in `data-insp-path`. Previously only simple identifiers like `<div>`
were captured; member expressions were silently dropped.
2. **React 19 jsx-dev-runtime patch** (`packages/vite`)
- React 19 removed `_debugSource` from fibers (facebook/react#32574).
The Vite plugin now patches `jsx-dev-runtime.js` at dev time to inject
the `source` parameter into `_debugInfo`, restoring file/line/column
info for the client to read. Supports React 19.0–19.2+.
- Adapted from vite-plugin-react-click-to-component by ArnaudBarre.
3. **Fiber-based component tree in context menu** (`packages/core/client`)
- The right-click "Click node to locate" panel now walks React's
`_debugOwner` chain to show actual component names (e.g. `<Dashboard>`,
`<UserProfile>`) instead of only DOM elements (`<div>`, `<span>`).
- Components from `node_modules` or with unresolvable names are filtered out.
- Falls back to the original DOM-based tree for non-React apps.
## Before / After
**Before:** tree shows mostly `<div>` tags with only a few component names that
happened to have `data-insp-path`.
**After:** tree shows the actual React component hierarchy with proper names,
making it far easier to navigate to the right source file.
Made-with: Cursor
Review Summary by QodoShow React component names in inspector and fix JSXMemberExpression support
WalkthroughsDescription• Add React fiber-based component tree to inspector context menu - Shows actual component names instead of just DOM tags - Walks React's _debugOwner chain for component hierarchy - Filters out node_modules and unresolvable components • Support JSXMemberExpression in data-insp-path injection - Handles <Typography.H2>, <Page.Content>, <Icons.Close /> patterns - Recursively resolves member expressions and namespaced names • Patch React 19 jsx-dev-runtime to restore source file information - Injects source parameter into _debugInfo for fiber source resolution - Supports React 19.0–19.1 and 19.2+ with different signatures • Fallback to DOM-based tree for non-React applications Diagramflowchart LR
A["JSX Transform"] -->|"Handle member expressions"| B["getJSXElementName()"]
B -->|"Inject data-insp-path"| C["DOM Elements"]
D["React 19 jsx-dev-runtime"] -->|"Patch source info"| E["_debugInfo"]
E -->|"Provide source data"| F["Client Inspector"]
C -->|"Extract fibers"| G["getReactFiber()"]
G -->|"Walk _debugOwner chain"| H["getLayersFromFiber()"]
H -->|"Build component tree"| I["generateFiberNodeTree()"]
I -->|"Display in context menu"| J["Inspector Panel"]
F -->|"Fallback if no fibers"| K["DOM-based tree"]
File Changes1. packages/core/src/client/index.ts
|
Code Review by Qodo
1. Namespaced JSX breaks parsing
|
| if (nameNode.type === 'JSXNamespacedName') { | ||
| return `${nameNode.namespace?.name || ''}:${nameNode.name?.name || ''}`; | ||
| } |
There was a problem hiding this comment.
1. Namespaced jsx breaks parsing 🐞 Bug ✓ Correctness
transformJsx() now injects data-insp-path names containing : for JSXNamespacedName, but the client parser splits on : and assumes the last 3 segments are [line, column, name]. This mis-parses line/column (NaN) and breaks locate/copy for namespaced JSX elements (e.g. svg:xlink).
Agent Prompt
## Issue description
`JSXNamespacedName` now produces a `nodeName` containing `:` (e.g. `svg:xlink`). This value is appended into `data-insp-path` using `:` as the delimiter, but the client splits `data-insp-path` on `:` and assumes the last 3 segments are `[line, column, name]`. That assumption breaks as soon as `name` itself contains `:`.
## Issue Context
- `transformJsx()` injects `data-insp-path="${filePath}:${line}:${column}:${nodeName}"`.
- `getSourceInfo()` parses with `paths.split(':')` and fixed indexing from the end.
## Fix Focus Areas
- packages/core/src/server/transform/transform-jsx.ts[13-27]
- packages/core/src/server/transform/transform-jsx.ts[62-69]
- packages/core/src/client/index.ts[577-593]
## Implementation guidance
Choose one:
1) **Change encoding**: represent namespaced JSX without `:` (e.g. `namespace.name`), so `data-insp-path` remains safely colon-delimited.
2) **Change decoding**: update `getSourceInfo()` to parse from the right by locating the last two numeric segments (line/column) and treat everything after them as `name` (joined with `:`), and everything before them as `path` (joined with `:`). Add a unit/integration test covering a namespaced JSX tag to prevent regressions.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Vite pre-bundles dependencies into chunk files (e.g. chunk-XXXXX.js), so the jsx-dev-runtime code ends up in a chunk without "jsx-dev-runtime" in the filename. Add content-based fallback detection for these chunks by checking for "_debugInfo" and "value: null" in the code content. Made-with: Cursor
c319f16 to
560f030
Compare
…nsp-path parsing data-insp-path uses colon as its delimiter (filePath:line:column:tagName). JSXNamespacedName produces names like `svg:xlink` which would break the client-side parser. Use dot separator instead (e.g. `svg.xlink`). Added a test to verify the separator is dot, not colon. Made-with: Cursor
|
Addressed the JSXNamespacedName parsing issue raised by Qodo bot — changed the separator from |
Review Summary by QodoShow React component names in inspector and fix JSXMemberExpression support
WalkthroughsDescription• Show actual React component names in inspector tree via fiber walking • Add JSXMemberExpression support for complex component tags • Patch React 19 jsx-dev-runtime to restore source info on fibers • Exclude jsx-dev-runtime from Vite pre-bundling with fallback detection Diagramflowchart LR
A["JSX Transform"] -->|"Handle JSXMemberExpression"| B["data-insp-path injection"]
C["React Fiber"] -->|"Walk _debugOwner chain"| D["Component hierarchy"]
E["React 19 jsx-dev-runtime"] -->|"Patch _debugInfo"| F["Source info restored"]
G["Vite Pre-bundling"] -->|"Exclude + Content fallback"| H["Reliable patch application"]
B --> I["Inspector Tree"]
D --> I
F --> I
H --> I
File Changes1. packages/core/src/client/index.ts
|
Code Review by Qodo
1. Unsafe React 19 runtime patch
|
| function patchReact19JsxDevRuntime(code: string): string | undefined { | ||
| if (code.includes('_source')) return undefined; // React <19, already has source | ||
|
|
||
| const defineIndex = code.indexOf('"_debugInfo"'); | ||
| if (defineIndex === -1) return undefined; | ||
| const valueIndex = code.indexOf('value: null', defineIndex); | ||
| if (valueIndex === -1) return undefined; | ||
|
|
||
| let patched = | ||
| code.slice(0, valueIndex) + 'value: source' + code.slice(valueIndex + 11); | ||
|
|
||
| // React 19.0-19.1: source is already a param of ReactElement | ||
| if (patched.includes('function ReactElement(type, key, self, source,')) { | ||
| return patched; | ||
| } | ||
|
|
||
| // React 19.2+: source needs to be threaded through jsxDEV → jsxDEVImpl → ReactElement | ||
| patched = patched.replace( | ||
| /maybeKey,\s*isStaticChildren/g, | ||
| 'maybeKey, isStaticChildren, source', | ||
| ); | ||
| patched = patched.replace( | ||
| /(\w+)?,\s*debugStack,\s*debugTask/g, | ||
| (m: string, previousArg: string) => { | ||
| if (previousArg === 'source') return m; | ||
| return m.replace('debugTask', 'debugTask, source'); | ||
| }, | ||
| ); |
There was a problem hiding this comment.
1. Unsafe react 19 runtime patch 🐞 Bug ✓ Correctness
patchReact19JsxDevRuntime() uses a whitespace-sensitive substring to detect the React 19.0–19.1 ReactElement signature, and then unconditionally applies global argument-threading replacements; if the signature check fails on transformed/minified code, the replacements can inject a duplicate source argument and break jsx-dev-runtime execution.
Agent Prompt
### Issue description
The React 19 patch relies on a brittle, formatting-dependent string match to decide whether to early-return for 19.0–19.1. If that check misses, the subsequent global regex rewrites can inject `source` even when it is already threaded, producing duplicated arguments and potentially breaking runtime JSX behavior.
### Issue Context
This plugin also patches pre-bundled/re-written `node_modules` chunks via a content-based fallback, where formatting is less predictable than the original source.
### Fix Focus Areas
- packages/vite/src/index.ts[16-49]
- packages/vite/src/index.ts[143-159]
### Suggested change
- Replace the whitespace-sensitive `includes('function ReactElement(type, key, self, source,')` with a regex tolerant to whitespace/minification, e.g. `function\s+ReactElement\(type\s*,\s*key\s*,\s*self\s*,\s*source\s*,`.
- Make the argument-threading replacements idempotent:
- Before replacing `maybeKey, isStaticChildren`, first check for an existing `maybeKey, isStaticChildren, source` (with flexible whitespace) and skip/avoid double insertion.
- Consider adding a second structural guard (e.g., presence of `jsxDEV` / `ReactElement` markers together) so the fallback patch cannot accidentally mutate unrelated `node_modules` chunks.
- Optionally detect already-patched code (`value: source`) and return `undefined` early to avoid re-processing.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
|
@zh-lx please can you review this? It's literally pain in the bum |
|
@Akshay090 Thanks for you PR. I'll check it today. |
|
Thanks for your PR. The primary reason why React component names are not currently displayed is that the dd attribute injected into components in React does not automatically inherit to the root node of the component. Therefore, as long as this issue is resolved, not only can component names be displayed in the right-click component tree, but also when hovering over the component root node with the left mouse button, component names can be displayed and the location of the component call can be pinpointed. Therefore, I implemented a simpler method in #513. |
|
resolved by #513 |
Summary
This PR improves the "Click node to locate" panel by showing actual React component names instead of just DOM tags, adds support for JSXMemberExpression in
data-insp-path, and restores React 19 compatibility for source file resolution.Changes
1. JSXMemberExpression support (
packages/core/src/server/transform/transform-jsx.ts)The Babel transform that injects
data-insp-pathpreviously only handledJSXIdentifier(simple tags like<div>,<Button>). JSXMemberExpression elements like<Typography.H2>,<Page.Content>,<Icons.Close />were silently dropped —nodeNameresolved to an empty string and the element was skipped.Added
getJSXElementName()that recursively handlesJSXIdentifier,JSXMemberExpression, andJSXNamespacedName. Includes 5 new test cases.2. React 19 jsx-dev-runtime patch (
packages/vite/src/index.ts)React 19 removed
_debugSourcefrom fiber nodes (facebook/react#32574), which broke source file resolution for tools that rely on it.The Vite plugin now intercepts
jsx-dev-runtime.jsduring dev and patches_debugInfoto carry thesourceparameter (fileName, lineNumber, columnNumber) thatjsxDEV()still receives but React 19 no longer stores. Supports React 19.0–19.1 and 19.2+ (different internal signatures).Adapted from vite-plugin-react-click-to-component by @ArnaudBarre.
3. Vite pre-bundling compatibility (
packages/vite/src/index.ts)Vite pre-bundles dependencies into chunk files (e.g.
chunk-XXXXX.js), so thejsx-dev-runtimecode can end up in a chunk where the filename no longer contains "jsx-dev-runtime". This caused the React 19 patch to silently not apply in real-world Vite projects.Fixed with two layers:
optimizeDeps.exclude: Excludesreact/jsx-dev-runtimefrom Vite's dependency pre-bundling so the transform hook can intercept it by filename_debugInfoproperty definition in code content for files undernode_modules4. Fiber-based component tree (
packages/core/src/client/index.ts)The right-click context menu ("Click node to locate") now walks React's
_debugOwnerchain to build the component tree, showing actual component names like<Dashboard>,<UserProfile>,<Card>instead of just<div>,<span>,<input>.__reactFiberto get fibers from DOM elements_debugOwnerfor the component hierarchy (naturally skips framework internals)_debugSource(React <19) or_debugInfo(React 19, via our patch) for file pathsnode_modulesor with unresolvable namesReact.memo,React.forwardRef,.WrappedComponent)Before / After
Before — tree shows mostly
<div>tags:After — tree shows actual React component hierarchy:
Testing
demos/vite-react) and React 19 (local demo)