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.
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
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
