Build a Vite Plugin for Instant Source Code Tracing in Vue Projects

This guide explains how to create a lightweight Vite plugin and a Tampermonkey script that embed absolute file paths into Vue components, enabling developers to click any element in the browser and instantly locate its source file in the project.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Build a Vite Plugin for Instant Source Code Tracing in Vue Projects

Background

Large monorepo front‑end projects contain thousands of .vue files, making it difficult to locate the source of a UI element, especially when taking over an unfamiliar project.

Goal

Provide a “what‑you‑see‑is‑what‑you‑get” system that shows which components compose a page, highlights the component hierarchy, and displays the absolute file path of a clicked element.

Vite plugin fundamentals

A Vite plugin is an object exported from a module and added to the plugins array in vite.config.ts. It can hook into lifecycle methods such as config, configResolved, resolveId, load, transform, buildStart, buildEnd, and closeBundle. The transform hook receives the raw source code and module ID before the official compiler runs, making it ideal for injecting custom attributes.

Why use the transform hook?

During development or build Vite processes each module through resolveId → load → transform. For Vue single‑file components each part (script, template, style) is transformed separately, so the transform hook runs multiple times for the same file, giving a perfect point to add metadata.

Implementation steps

Write a custom Vite plugin that runs in the transform phase for .vue files.

Parse the component’s template AST to locate the root element.

Encode the file’s absolute path with LZString.compressToBase64 and add it as a custom attribute (e.g., csc-mark) to the root element.

Return the modified source code for the rest of the build pipeline.

Vite plugin code

export function cscMark(): Plugin {
  return {
    name: 'csc-mark',
    enforce: 'pre',
    transform(code, id) {
      if (!id.endsWith('.vue')) return null;
      const { template } = parse(code, { filename: id }).descriptor;
      if (template) {
        const elm = template.ast.children.find(item => item.type === NodeTypes.ELEMENT) as ElementNode | undefined;
        if (elm) {
          const tagString = `<${elm.tag}`;
          const insertIndex = elm.loc.source.indexOf(tagString) + tagString.length;
          const newSource = `${elm.loc.source.slice(0, insertIndex)} csc-mark="${LZString.compressToBase64(id)}"${elm.loc.source.slice(insertIndex)}`;
          code = code.replace(elm.loc.source, newSource);
        }
      }
      return code;
    }
  };
}

Node‑transform for Vue compiler

export const cscMarkNodeTransform = (node, context) => {
  if (node.type === NodeTypes.ELEMENT && context.parent) {
    if ([NodeTypes.ROOT, NodeTypes.IF_BRANCH].includes(context.parent.type)) {
      const firstElm = context.parent.children.find(item => item.type === NodeTypes.ELEMENT) as ElementNode | undefined;
      const addText = firstElm?.props.find(p => p.name === 'csc-mark')?.value?.content || '';
      if (addText) addClass(node, addText, 'class');
    } else if (context.parent.props?.find(p => p.name === 'csc-mark')?.value?.content) {
      const addText = context.parent.props.find(p => p.name === 'csc-mark')?.value?.content || '';
      if (addText) addClass(node, addText, 'class');
    }
  }
};

Tampermonkey script

The script adds an “inspect” button, highlights elements that carry the generated class ( css-vite-mark-*), and shows a dialog with the component hierarchy and decoded absolute paths.

// Collect csc‑mark hierarchy from a clicked element
function collectCscMarkHierarchy(element) {
  const list = [];
  while (element) {
    if (element.hasAttribute('csc-mark')) {
      list.push({ element, mark: element.getAttribute('csc-mark') });
    }
    element = element.parentElement;
  }
  return list;
}

// Decode the base64 path
function decodePath(encoded) {
  try { return LZString.decompressFromBase64(encoded); }
  catch (e) { console.error('Decode failed:', e); return null; }
}

// Click handler
function handleClick(event) {
  let el = event.target;
  while (el && !el.hasAttribute('csc-mark')) el = el.parentElement;
  if (el && el.hasAttribute('csc-mark')) {
    event.stopPropagation();
    event.preventDefault();
    const hierarchy = collectCscMarkHierarchy(el);
    showCustomDialog(hierarchy);
  }
}

Usage workflow

Scaffold a vue+vite+pnpm project (e.g., pnpm create vite).

Add the custom plugin to vite.config.ts and install the Tampermonkey script.

Start the dev server, click the injected “inspect” button, then click any component to see its absolute path and hierarchy.

Conclusion

The approach demonstrates how Vite’s lifecycle and custom plugins can embed source‑file metadata into compiled code, and how a lightweight userscript can read that metadata in the browser to provide instant navigation back to the original file. Source code: https://github.com/hjj5201/vite-plugin

ASTVitePlugin DevelopmentTampermonkeySource Code TracingTransform Hook
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.