Click to Source: Embedding Runtime Locations
October 20, 2025
Click to Source: Embedding Runtime Locations#
Build-time transforms inject source locations into runtime code. Alt+Click any element to jump to its source file.
Try It#
Alt+ Hover to inspect•Alt+ Click to view source
Interactive Demo
Console
Waiting for interaction...
Alt + Click on a component to view its source
React Component Locations#
Transform $ prop shorthand into data-loc attributes:
if (path.endsWith(".tsx")) {
for (const match of code.matchAll(CLASSNAME_$_RE)) {
const index = match.index!;
const [_, pre, post] = match;
let dataAttrs = "";
if (enableReact$Loc) {
const { line, column } = indexToLineAndColumn(code, index + pre.length);
const link = `${linkPath}:${line}:${column}`;
dataAttrs = `data-loc=${JSON.stringify(link)}`;
if (pre.startsWith("<") && !pre.startsWith("<div")) {
dataAttrs += ` data-name=${JSON.stringify(pre.split(" ")[0].slice(1).trim())}`;
}
}
if (post === "=") {
const replacement = dataAttrs ? `${dataAttrs} className` : "className";
string.overwrite(index + pre.length, index + _.length - post.length, replacement);
} else {
string.overwrite(index + pre.length, index + _.length - post.length, dataAttrs);
}
}
} // Input:
<div className="card">Content</div>
// Output (at build):
<div data-loc="components/Card.tsx:26:7" className="card">Content</div>Runtime handler walks React fiber tree and opens the file:
const getPath = (fiber: Fiber, element: HTMLElement | undefined): string | undefined => {
// First check for data-loc attribute if element is provided
if (element?.dataset.loc) {
return element.dataset.loc;
}
const source = fiber._debugSource ?? fiber._debugInfo ?? fiber._source;
if (!source) return undefined;
const { fileName, lineNumber = 1, columnNumber = 1 } = source;
return `${fileName}:${lineNumber}:${columnNumber}`;
};
const getLayersForElement = (element: HTMLElement, root: string): ComponentLayer[] => {
let instance = getReactInstanceForElement(element);
const layers: ComponentLayer[] = [];
while (instance) {
// Try to find the DOM element for this fiber to check for data-loc
const fiberElement = instance.stateNode instanceof HTMLElement ? instance.stateNode : undefined;
const path = getPath(instance, fiberElement);
if (path) {
const name =
typeof instance.type === "string"
? instance.type
: (instance.type.displayName ?? instance.type.name ?? instance.type.render?.name ?? "undefined");
layers.push({ name, path: path.replace(`${root}/`, "") });
}
instance = instance._debugOwner ?? undefined;
}
return layers;
};DevString Locations#
Tagged templates automatically capture call-site locations:
if (enableDevLoc) {
for (const match of code.matchAll(DEV_RE)) {
const index = match.index!;
const { line, column } = indexToLineAndColumn(code, index);
const [_, fn, message] = match;
const link = `${linkPath}:${line}:${column}`;
string.overwrite(index, index + _.length, `${fn}\`${message}\`.ctx({ loc: ${JSON.stringify(link)} })`);
}
}// Input:
handler(dev`User pressed X`);
// Output (at build):
handler(dev`User pressed X`.ctx({ loc: "Card.tsx:83:12" }));Usage:
function deleteCard(reason: DevString) {
console.log("Called from:", reason.toJSON().context.loc);
}
deleteCard(dev`User clicked delete`);
// Automatically knows source locationDevStrings compose with .because() to build audit trails:
const rootEvent = dev`keydown from root`;
handler(rootEvent.because(dev`Looking for action handler`));
// Creates chain: "keydown from root → Looking for action handler"Editor Integration#
Local HTTP endpoint opens files:
let lastOpenedFile: string | null = null;
/**
* uses our launch-editor endpoint to open the file in the dev's editor
* this has been set up as a vite plugin.
*/
export const openInDevEditor = (loc: string) => {
if (lastOpenedFile === loc) return;
lastOpenedFile = loc;
setTimeout(() => (lastOpenedFile = null), 500);
void fetch(`http://localhost:5090/__open-in-editor?file=${loc}`).catch((error) => {
console.error("Failed to open in editor", error);
});
};openInDevEditor("Card.tsx:26:7");
↓
fetch("http://localhost:5090/__open-in-editor?file=Card.tsx:26:7");
↓
// Editor opens at that lineSetup#
Build plugin:
import { bunDevStringAndReact$ClassNameLoc } from "./bun-dev-and-react-$-className-loc.mts";
await Bun.build({
plugins: [
bunDevStringAndReact$ClassNameLoc({
enableReact$Loc: true,
enableDevLoc: true,
}),
],
});Runtime:
import { initClickToSource } from "./click-to-source.client.ts";
if (import.meta.env.DEV) {
initClickToSource();
}