Initial commit of template
This commit is contained in:
103
plugins/component-tagger.ts
Normal file
103
plugins/component-tagger.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { parse } from "@babel/parser";
|
||||
import { walk } from "estree-walker";
|
||||
import MagicString from "magic-string";
|
||||
import path from "node:path";
|
||||
import type { Plugin } from "vite";
|
||||
|
||||
const VALID_EXTENSIONS = new Set([".jsx", ".tsx"]);
|
||||
|
||||
/**
|
||||
* Returns a Vite plugin that adds component data attributes for component selection.
|
||||
*/
|
||||
export default function componentTagger(): Plugin {
|
||||
return {
|
||||
name: "vite-plugin-component-tagger",
|
||||
apply: () => true,
|
||||
enforce: "pre",
|
||||
|
||||
async transform(code: string, id: string) {
|
||||
try {
|
||||
// Ignore non-jsx files and files inside node_modules
|
||||
if (
|
||||
!VALID_EXTENSIONS.has(path.extname(id)) ||
|
||||
id.includes("node_modules")
|
||||
)
|
||||
return null;
|
||||
|
||||
const ast = parse(code, {
|
||||
sourceType: "module",
|
||||
plugins: ["jsx", "typescript"],
|
||||
});
|
||||
|
||||
const ms = new MagicString(code);
|
||||
const fileRelative = path.relative(process.cwd(), id);
|
||||
|
||||
walk(ast as any, {
|
||||
enter(node: any) {
|
||||
try {
|
||||
if (node.type !== "JSXOpeningElement") return;
|
||||
|
||||
// ── 1. Extract the tag / component name ──────────────────────────────
|
||||
if (node.name?.type !== "JSXIdentifier") return;
|
||||
const tagName = node.name.name as string;
|
||||
if (!tagName) return;
|
||||
|
||||
// Skip certain HTML elements that are too generic
|
||||
const skipElements = new Set([
|
||||
"html",
|
||||
"head",
|
||||
"body",
|
||||
"script",
|
||||
"style",
|
||||
"meta",
|
||||
"link",
|
||||
"title",
|
||||
]);
|
||||
if (skipElements.has(tagName.toLowerCase())) return;
|
||||
|
||||
// ── 2. Check whether the tag already has data-component-id ───────────────
|
||||
const alreadyTagged = node.attributes?.some(
|
||||
(attr: any) =>
|
||||
attr.type === "JSXAttribute" &&
|
||||
attr.name?.name === "data-component-id",
|
||||
);
|
||||
if (alreadyTagged) return;
|
||||
|
||||
// ── 3. Build the id "relative/file.jsx:line:column" ─────────────────
|
||||
const loc = node.loc?.start;
|
||||
if (!loc) return;
|
||||
const componentId = `${fileRelative}:${loc.line}:${loc.column}`;
|
||||
|
||||
// ── 4. Inject the attributes just after the tag name ────────────────
|
||||
if (node.name.end != null) {
|
||||
ms.appendLeft(
|
||||
node.name.end,
|
||||
` data-component-id="${componentId}" data-component-name="${tagName}"`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`[component-tagger] Warning: Failed to process JSX node in ${id}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// If nothing changed bail out.
|
||||
if (ms.toString() === code) return null;
|
||||
|
||||
return {
|
||||
code: ms.toString(),
|
||||
map: ms.generateMap({ hires: true }),
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`[component-tagger] Warning: Failed to transform ${id}:`,
|
||||
error,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user