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 inspectAlt+ 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 location

DevStrings 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 line

Setup#

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();
}