react-easy-debug-source
v0.1.1
Published
AI-native debugging bridge for React - click any UI element to generate precise code location + runtime state context for AI assistants
Maintainers
Readme
react-easy-debug-source
AI-native debugging bridge for React -- click any UI element, get precise source location + component name for AI assistants.
React 的 AI 原生调试桥接库 -- 点击任意 UI 元素,获取精确的源码位置 + 组件名,供 AI 助手使用。
Features
- Babel Plugin (compile-time) -- injects
data-ai-sourceanddata-ai-componentattributes into all JSX elements - Runtime Inspector (browser) -- keyboard shortcut triggers inspect mode, hover to highlight, click to copy source location
- Two-Tier Lookup -- DOM attribute walk + React Fiber tree fallback, works with MUI / Ant Design / any component library
- Zero production overhead -- Babel plugin returns empty visitor in production mode
- ESM + CJS -- dual format output, works with any bundler
Install
# pnpm (recommended)
pnpm add react-easy-debug-source
# npm
npm install react-easy-debug-source
# yarn
yarn add react-easy-debug-sourcePeer dependencies: react >= 16.8.0, react-dom >= 16.8.0
Quick Start
Step 1: Configure the Babel Plugin
Add the Babel plugin to your vite.config.ts:
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { fileURLToPath } from 'url'
import { dirname, resolve } from 'path'
const __dirname = dirname(fileURLToPath(import.meta.url))
export default defineConfig({
plugins: [
react({
babel: {
plugins: [
[
'react-easy-debug-source/babel-plugin',
{
// Option 1 (recommended): project name prefix + monorepo root
projectName: 'my-app',
root: resolve(__dirname, '../..'),
// Option 2: use absolute paths
// useAbsolutePath: true,
},
],
],
},
}),
],
})Babel Plugin Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| root | string | state.cwd \|\| process.cwd() | Root directory for computing relative file paths |
| projectName | string | -- | Path prefix, e.g. 'example' produces example:src/App.tsx:10:20 |
| useAbsolutePath | boolean | false | Use absolute paths instead of relative paths |
Injected Attributes
The plugin injects two attributes on all JSX elements (native HTML + React components like MUI):
data-ai-source="[projectName:]path/to/file.tsx:startLine:endLine"-- source file and line rangedata-ai-component="ComponentName"-- enclosing React component name
Skip rules:
- Production mode (
NODE_ENV === 'production') -- no injection <Fragment>-- skippedJSXMemberExpression(e.g.<React.Fragment>) -- skipped- Elements already having
data-ai-source-- no duplicate injection
Step 2: Initialize the Runtime Inspector
Simple Approach (in main.tsx)
// main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
// Initialize only in development mode
if (import.meta.env.DEV) {
import('react-easy-debug-source').then(({ initAIDebug }) => {
initAIDebug({
shortcut: 'Ctrl+Shift+I', // optional, default 'Alt+Shift+D'
enabled: true, // optional, default true
})
})
}
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)Advanced Approach: boot.ts Pattern (recommended)
For full Fiber-based fallback support (Tier 2 lookup), installDevToolsHook() must be called before React loads. Use a separate boot file as the entry point:
// src/boot.ts -- application entry point
// IMPORTANT: this file must NOT contain any static React imports
if (import.meta.env.DEV) {
import('react-easy-debug-source').then(async ({ initAIDebug, installDevToolsHook }) => {
// Phase 1: install DevTools hook before React loads
await installDevToolsHook();
// Phase 2: initialize the inspector
initAIDebug({
shortcut: 'Ctrl+Shift+I',
})
})
}
// React and app code load after hook installation
import('./main')// src/main.tsx -- React entry (loaded dynamically by boot.ts)
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)Update index.html to load boot.ts instead of main.tsx:
<!-- index.html -->
<script type="module" src="/src/boot.ts"></script>Why the boot.ts pattern?
installDevToolsHook()patcheswindow.__REACT_DEVTOOLS_GLOBAL_HOOK__so React registers itself when it loads. This enables the Tier 2 Fiber-based lookup for elements that don't havedata-ai-*DOM attributes (e.g. MUI internal sub-elements, Portals). If you skip this step, Tier 1 DOM attribute lookup still works for most elements.
pnpm note: In a pnpm workspace,
react-devtools-inline(a dependency ofreact-easy-debug-source) has a peer dependency onreact. Due to pnpm strict isolation, you may need to addreact-devtools-inlineas a direct dependency of your app, or configure.npmrcwithpublic-hoist-pattern[]=react.
Step 3: Use the Inspector
- Press the keyboard shortcut (default
Alt+Shift+D) to enter inspect mode - Hover over elements -- highlighted with a blue semi-transparent box, label shows
ComponentName -> <tagName> WxH - Click an element -- source info is copied to clipboard in format
ComponentName - path/to/file.tsx:startLine:endLine - Press
ESCto exit inspect mode (without copying)
API Reference
Exports from react-easy-debug-source
import { initAIDebug, installDevToolsHook } from 'react-easy-debug-source'
import type { AIDebugOptions, InspectModeOptions } from 'react-easy-debug-source'initAIDebug(options?: AIDebugOptions): () => void
Initialize the inspector. Waits for DOM ready, creates an InspectMode instance, and mounts it to window.__inspectMode.
interface AIDebugOptions {
/** Keyboard shortcut, default 'Alt+Shift+D' */
shortcut?: string
/** Enable inspector, default true */
enabled?: boolean
}Returns: cleanup function -- call it to destroy the inspector and remove all event listeners.
installDevToolsHook(): Promise<void>
Install React DevTools Hook. Must be called before React loads. Internally calls react-devtools-inline/backend's initialize(window).
Note: The current Tier 2 lookup uses direct Fiber access (
__reactFiber$), so this function is optional. It is retained as a public API for advanced use cases.
InspectMode class (via window.__inspectMode)
class InspectMode {
toggle(): void // Toggle inspect mode on/off
start(): void // Enter inspect mode
stop(): void // Exit inspect mode
isActive(): boolean // Whether inspect mode is active
destroy(): void // Destroy instance, remove all event listeners
}Export from react-easy-debug-source/babel-plugin
Default export is a Babel plugin. Used as a plugin specifier string in build tool configuration (see Step 1).
Keyboard Shortcut Format
Connect modifier keys and a regular key with +, case-insensitive:
'Alt+Shift+D' -- Alt + Shift + D (default)
'Ctrl+Shift+I' -- Ctrl + Shift + I
'Meta+K' -- Cmd + K (macOS)
'Ctrl+Alt+Shift+X' -- Ctrl + Alt + Shift + XSupported modifiers: Alt, Shift, Ctrl/Control, Meta/Cmd
Advanced Usage
Monorepo Setup
In a monorepo, configure the Babel plugin in each React app with a unique projectName:
// packages/app-a/vite.config.ts
['react-easy-debug-source/babel-plugin', {
projectName: 'app-a',
root: resolve(__dirname, '../..'),
}]
// packages/app-b/vite.config.ts
['react-easy-debug-source/babel-plugin', {
projectName: 'app-b',
root: resolve(__dirname, '../..'),
}]Generated attributes:
data-ai-source="app-a:packages/app-a/src/App.tsx:10:20"data-ai-source="app-b:packages/app-b/src/Login.tsx:5:15"
Programmatic Toggle
// Toggle via button (no keyboard shortcut needed)
button.addEventListener('click', () => {
window.__inspectMode?.toggle()
})
// Or in React
<button onClick={() => (window as any).__inspectMode?.toggle()}>
Toggle Inspect Mode
</button>You can also toggle from the browser console:
window.__inspectMode.toggle()Working with Component Libraries (MUI / Ant Design / etc.)
The Babel plugin injects attributes on all JSX elements, including component library usage:
// Your code
<Box sx={{ display: 'flex' }}>
<Typography>Hello</Typography>
</Box>
// After Babel compilation
<Box data-ai-source="app:src/App.tsx:10:12" data-ai-component="MyComp" sx={{ display: 'flex' }}>
<Typography data-ai-source="app:src/App.tsx:11:11" data-ai-component="MyComp">Hello</Typography>
</Box>Most MUI components forward data-* attributes to the DOM, so Tier 1 lookup works directly. For MUI internal sub-elements (e.g. MuiTabs-indicator, MuiTabs-scroller) where DOM attributes are not reachable, Tier 2 walks the React Fiber tree to find the nearest ancestor with data-ai-source.
Working with React Compiler
This plugin works alongside babel-plugin-react-compiler:
babel: {
plugins: [
['react-easy-debug-source/babel-plugin', { projectName: 'example' }],
['babel-plugin-react-compiler'],
],
}How It Works
Compile time: Runtime:
<Box sx={...}> Babel Plugin <div data-ai-source="example:src/App.tsx:59:98"
... ──────────> data-ai-component="AppContent"
</Box> class="MuiBox-root">
Inspector click -> copy "AppContent - example:src/App.tsx:59:98"Two-Tier Lookup
When the user clicks/hovers a DOM element:
- Tier 1 (DOM walk): Walk up the DOM tree looking for
data-ai-sourceattribute. Works for native HTML elements and components that forwarddata-*attributes. - Tier 2 (Fiber walk, fallback): Access the React Fiber tree via
__reactFiber$on the DOM element, walkfiber.returnchain andfiber._debugOwnerchain looking formemoizedProps['data-ai-source']. Works for component library internals, Portals, and elements where DOM attributes are unreachable.
Component Name Resolution (Babel Plugin)
The plugin walks up the AST to find the enclosing component:
FunctionDeclaration--function MyComponent() {}ArrowFunctionExpression/FunctionExpressionassigned toVariableDeclarator--const MyComponent = () => {}ClassDeclaration--class MyComponent extends React.Component {}ClassMethod-- walks up to the parentClassDeclaration- Fallback: derives name from the filename (strips extension)
Project Structure
packages/react-easy-debug-source/
├── src/
│ ├── index.ts # Entry: initAIDebug, installDevToolsHook
│ ├── inspector.ts # InspectMode class: UI + two-tier lookup
│ ├── babel-plugin.ts # Babel plugin: inject data-ai-* attributes
│ ├── react-devtools-inline.d.ts # Type declaration for react-devtools-inline
│ └── babel-helper-plugin-utils.d.ts # Type declaration for @babel/helper-plugin-utils
├── dist/ # Build output (tsup)
│ ├── index.mjs / index.cjs # Runtime (ESM / CJS)
│ ├── index.d.ts / index.d.cts # Type declarations
│ ├── babel-plugin.mjs / .cjs # Babel plugin (ESM / CJS)
│ └── babel-plugin.d.ts / .d.cts # Babel plugin type declarations
├── tsup.config.ts # Build config (dual entry)
├── tsconfig.json # TypeScript config
└── package.json # Package configBuild
# Build the library
pnpm --filter react-easy-debug-source build
# Watch mode
pnpm --filter react-easy-debug-source devTroubleshooting
Keyboard shortcut not working?
Some OS / browser combinations reserve certain shortcuts. Try a different combination:
initAIDebug({
shortcut: 'Ctrl+Shift+I', // recommended
})Suggested alternatives: Ctrl+Shift+I, Ctrl+Shift+K, Ctrl+Shift+E, F9, Alt+Shift+K
Avoid: Alt+Shift+D or Alt+K -- may conflict with system shortcuts on some keyboard layouts.
Manual toggle from console
window.__inspectMode.toggle()pnpm strict isolation issues
If react-devtools-inline fails to resolve react in a pnpm workspace, add to your root .npmrc:
public-hoist-pattern[]=react
public-hoist-pattern[]=react-domOr add react-devtools-inline as a direct dependency of your app package.
License
MIT
