react-code-locator
v0.4.7
Published
A package that lets you right-click any element in your React app during development to jump directly to its source code location.
Readme
React Code Locator
A package that lets you right-click any element in your React app during development to jump directly to its source code location.
- Zero dependencies: No Babel, no React DevTools, no browser extension — just one build plugin.
- React 19 support:
fiber._debugSourcewas removed in React 19. This package injects source metadata at build time, so it works regardless of the React version. - Universal: Supports Vite, Webpack, Rollup, esbuild, and Rspack.
- Dev only: No impact on production builds.
Installation
npm i -D react-code-locatorQuick Start
Vite
vitePlugin handles both source transform and automatic client runtime injection.
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { vitePlugin } from "react-code-locator";
export default defineConfig({
plugins: [
react(),
vitePlugin(),
],
});Next.js (Webpack)
// next.config.js
const { webpackPlugin } = require("react-code-locator");
module.exports = {
webpack(config) {
config.plugins.push(webpackPlugin());
return config;
},
};Webpack mode only — Turbopack is not supported. Under Turbopack, Next.js never calls the
webpack()hook innext.config.js, so the plugin is never loaded and never runs. There is no error and no warning — the app boots normally and clicking simply does nothing. If locating stops working after a Next.js upgrade, this is almost certainly why: Turbopack became the default bundler in Next.js 16, and was opt-in via--turbopackin Next.js 15.Run the dev server in webpack mode instead:
next dev --webpack # Next.js 16+: opt back out of Turbopack next dev # Next.js 15 and below: webpack is already the defaultThe cause is upstream:
unpluginships vite, rollup, webpack, rspack and esbuild adapters, but no Turbopack adapter, because Turbopack has no public plugin API for source transforms yet. Support will land here once one exists.
Create React App
// config-overrides.js
const { webpackPlugin } = require("react-code-locator");
module.exports = {
webpack(config) {
config.plugins.push(webpackPlugin());
return config;
},
};Rollup
// rollup.config.js
import { rollupPlugin } from "react-code-locator";
export default {
plugins: [rollupPlugin()],
};esbuild
import { esbuildPlugin } from "react-code-locator";
await esbuild.build({
plugins: [esbuildPlugin()],
});Rspack
// rspack.config.js
const { rspackPlugin } = require("react-code-locator");
module.exports = {
plugins: [rspackPlugin()],
};Options
// Options shared by all plugins
webpackPlugin({
// Whether to enable the plugin (default: NODE_ENV === "development")
// Set this explicitly if you use a custom environment variable instead of NODE_ENV.
enabled: process.env.MY_ENV === "dev",
projectRoot: process.cwd(), // Project root — base path for resolving source locations (default: process.cwd())
});
// Vite-only additional options
vitePlugin({
enabled: process.env.MY_ENV === "dev",
projectRoot: process.cwd(),
// injectClient: true (default) — automatically injects enableReactComponentJump() into the HTML.
// injectClient: false — disables auto injection; call enableReactComponentJump() manually.
injectClient: true,
editor: "code", // Editor to open files in (default: "code"). See supported editors below.
locator: { // Runtime options (injected automatically when injectClient: true)
triggerKey: "shift", // Trigger key: "alt" | "meta" | "ctrl" | "shift" | "none" (default: "shift")
projectRoot: process.cwd(), // Base path for normalizing source paths (default: not set)
openInEditor: true, // Show "Open in editor" in the right-click menu (default: false)
onLocate(result) {}, // Callback when a source location is found
onError(error) {}, // Callback on error
},
});Usage
Hover Highlight
Hold the trigger key (default: Shift) and hover over an element. The element will be highlighted in blue like a DevTools inspector, showing the component file name and line number.
Right-Click Context Menu
Hold the trigger key and right-click any element to open the context menu.
┌─────────────────────────────────┐
│ Open in editor │ ← openInEditor: true 일 때 표시
│ Copy path │
└─────────────────────────────────┘| Item | Action |
|------|--------|
| Open in editor | Opens the source file in your editor and jumps to the exact line (shown when openInEditor: true) |
| Copy path | Copies the source path in file:line:col format to the clipboard |
The browser's default context menu is automatically suppressed.
Keyboard Shortcuts
| Key | Action |
|-----|--------|
| Shift + Click | Print source location to the console |
| Alt + 1 | Screen mode (components visible on screen, default) |
| Alt + 2 | Implementation mode (implementation location) |
Opening in Editor
Set openInEditor: true to show the "Open in editor" option in the right-click menu.
Vite
The /__open-in-editor endpoint is automatically registered by vitePlugin. No extra setup needed.
// vite.config.ts
vitePlugin({
editor: "code", // editor command (default: "code")
locator: {
openInEditor: true,
},
})Webpack / Rspack
Add openInEditorMiddleware to your devServer and pass the editor command.
// webpack.config.js
const { webpackPlugin, openInEditorMiddleware } = require("react-code-locator");
module.exports = {
plugins: [webpackPlugin()],
devServer: {
setupMiddlewares(middlewares) {
middlewares.unshift({
name: "open-in-editor",
path: "/__open-in-editor",
middleware: openInEditorMiddleware("code"), // pass your editor command
});
return middlewares;
},
},
};// main.tsx
import { enableReactComponentJump } from "react-code-locator";
enableReactComponentJump({ openInEditor: true });Supported Editors
| Editor | Command |
|--------|---------|
| VS Code | "code" |
| VS Code Insiders | "code-insiders" |
| Cursor | "cursor" |
| VSCodium | "codium" / "vscodium" |
| WebStorm | "webstorm" |
| IntelliJ IDEA | "idea" |
| GoLand | "goland" |
| PyCharm | "pycharm" |
| PhpStorm | "phpstorm" |
| RubyMine | "rubymine" |
| CLion | "clion" |
| Rider | "rider" |
| Zed | "zed" |
| Sublime Text | "subl" |
| Atom | "atom" |
| Vim | "vim" |
| Emacs | "emacs" |
Manual Setup
Disable auto injection with injectClient: false and call enableReactComponentJump manually to control activation. This approach is also used in non-Vite environments.
// vite.config.ts
vitePlugin({ injectClient: false })// main.tsx
import { enableReactComponentJump } from "react-code-locator";
enableReactComponentJump({
enabled: true, // default: true. Set to false to disable.
triggerKey: "shift", // "alt" | "meta" | "ctrl" | "shift" | "none" (default: "shift")
projectRoot: "/path/to/project", // Base path for normalizing source paths (optional)
openInEditor: true, // Show "Open in editor" in the right-click menu
onLocate(result) {
console.log("Source:", result.source); // result.source, result.mode
},
onError(error) {
console.error("Error:", error);
},
});What Gets Detected
Every JSX element gets its source location registered, so clicking any rendered
markup resolves. On top of that, these component declaration forms are annotated
with their definition site (used by implementation mode, Alt+2):
| Form | Example |
| --- | --- |
| Function declaration | function Card() {} |
| Arrow / function expression | const Card = () => {} |
| Class component | class Card extends React.Component {} |
| memo / forwardRef | const Card = memo(...) |
| Any HOC call | const Card = withAuth(Base) |
| Tagged template | const Card = styled(Base)`...` |
| Nested (non-top-level) | function Outer() { const Row = () => {} } |
Detection keys off the capitalized-name convention: only identifiers starting with an uppercase letter are treated as components. Classes are only annotated when they extend a base class, so plain data classes are left alone.
File types: .tsx, .jsx, .js and .ts. JSX wrapping runs on everything except
.ts (which cannot contain JSX, and where the JSX parser would misread <T,>
generic arrow functions).
Known Limitations
- React Native not supported: Relies on the DOM API.
- Turbopack not supported: Turbopack has no public plugin API for source transforms, and
unpluginhas no Turbopack adapter as a result. Under Turbopack the plugin is never loaded and fails silently — see the Next.js section for the--webpackworkaround. - Disabled elements / blocked pointer-events: Elements with a
disabledattribute orpointer-events: noneapplied will not fire click events and cannot be detected. - CRA (Create React App): The webpack config is hidden, so plugin injection requires
react-app-rewiredorcraco. - Dev only: The plugin's
enabledoption defaults toNODE_ENV === "development", so it is automatically disabled in production builds.
License
MIT
