UNPKG

lovable-tagger

Version:

Vite plugin for component tagging

426 lines (421 loc) 13.6 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { componentTagger: () => componentTagger }); module.exports = __toCommonJS(src_exports); // src/componentTaggerPlugin.ts var import_promises = __toESM(require("fs/promises"), 1); var import_path2 = __toESM(require("path"), 1); var import_parser = require("@babel/parser"); var esbuild = __toESM(require("esbuild"), 1); var import_magic_string = __toESM(require("magic-string"), 1); var import_resolveConfig = __toESM(require("tailwindcss/resolveConfig.js"), 1); // src/util.ts var import_fs = require("fs"); var import_path = __toESM(require("path"), 1); function findProjectRoot(startPath = process.cwd()) { try { let currentPath = startPath; let count = 0; while (currentPath !== import_path.default.parse(currentPath).root && count < 20) { if ((0, import_fs.existsSync)(import_path.default.join(currentPath, "package.json"))) { return currentPath; } currentPath = import_path.default.dirname(currentPath); count++; } return process.cwd(); } catch (error) { console.error("Error finding project root:", error); return process.cwd(); } } var threeFiberElems = /* @__PURE__ */ new Set([ "object3D", "audioListener", "positionalAudio", "mesh", "batchedMesh", "instancedMesh", "scene", "sprite", "lOD", "skinnedMesh", "skeleton", "bone", "lineSegments", "lineLoop", "points", "group", "camera", "perspectiveCamera", "orthographicCamera", "cubeCamera", "arrayCamera", "instancedBufferGeometry", "bufferGeometry", "boxBufferGeometry", "circleBufferGeometry", "coneBufferGeometry", "cylinderBufferGeometry", "dodecahedronBufferGeometry", "extrudeBufferGeometry", "icosahedronBufferGeometry", "latheBufferGeometry", "octahedronBufferGeometry", "planeBufferGeometry", "polyhedronBufferGeometry", "ringBufferGeometry", "shapeBufferGeometry", "sphereBufferGeometry", "tetrahedronBufferGeometry", "torusBufferGeometry", "torusKnotBufferGeometry", "tubeBufferGeometry", "wireframeGeometry", "tetrahedronGeometry", "octahedronGeometry", "icosahedronGeometry", "dodecahedronGeometry", "polyhedronGeometry", "tubeGeometry", "torusKnotGeometry", "torusGeometry", "sphereGeometry", "ringGeometry", "planeGeometry", "latheGeometry", "shapeGeometry", "extrudeGeometry", "edgesGeometry", "coneGeometry", "cylinderGeometry", "circleGeometry", "boxGeometry", "capsuleGeometry", "material", "shadowMaterial", "spriteMaterial", "rawShaderMaterial", "shaderMaterial", "pointsMaterial", "meshPhysicalMaterial", "meshStandardMaterial", "meshPhongMaterial", "meshToonMaterial", "meshNormalMaterial", "meshLambertMaterial", "meshDepthMaterial", "meshDistanceMaterial", "meshBasicMaterial", "meshMatcapMaterial", "lineDashedMaterial", "lineBasicMaterial", "primitive", "light", "spotLightShadow", "spotLight", "pointLight", "rectAreaLight", "hemisphereLight", "directionalLightShadow", "directionalLight", "ambientLight", "lightShadow", "ambientLightProbe", "hemisphereLightProbe", "lightProbe", "spotLightHelper", "skeletonHelper", "pointLightHelper", "hemisphereLightHelper", "gridHelper", "polarGridHelper", "directionalLightHelper", "cameraHelper", "boxHelper", "box3Helper", "planeHelper", "arrowHelper", "axesHelper", "texture", "videoTexture", "dataTexture", "dataTexture3D", "compressedTexture", "cubeTexture", "canvasTexture", "depthTexture", "raycaster", "vector2", "vector3", "vector4", "euler", "matrix3", "matrix4", "quaternion", "bufferAttribute", "float16BufferAttribute", "float32BufferAttribute", "float64BufferAttribute", "int8BufferAttribute", "int16BufferAttribute", "int32BufferAttribute", "uint8BufferAttribute", "uint16BufferAttribute", "uint32BufferAttribute", "instancedBufferAttribute", "color", "fog", "fogExp2", "shape", "colorShiftMaterial" ]); function shouldTagElement(elementName, threeDreiImportedElements, threeDreiNamespaces) { if (threeFiberElems.has(elementName)) { return false; } if (threeDreiImportedElements.has(elementName)) { return false; } if (elementName.includes(".")) { const namespace = elementName.split(".")[0]; if (threeDreiNamespaces.has(namespace)) { return false; } } return true; } // src/componentTaggerPlugin.ts var validExtensions = /* @__PURE__ */ new Set([".jsx", ".tsx"]); var projectRoot = findProjectRoot(); var tailwindInputFile = import_path2.default.resolve(projectRoot, "./tailwind.config.ts"); var tailwindJsonOutfile = import_path2.default.resolve( projectRoot, "./src/tailwind.config.lov.json" ); var tailwindIntermediateFile = import_path2.default.resolve( projectRoot, "./.lov.tailwind.config.js" ); var isSandbox = process.env.LOVABLE_DEV_SERVER === "true"; function componentTagger() { const cwd = process.cwd(); const stats = { totalFiles: 0, processedFiles: 0, totalElements: 0 }; return { name: "vite-plugin-component-tagger", enforce: "pre", async transform(code, id) { if (!validExtensions.has(import_path2.default.extname(id)) || id.includes("node_modules")) { return null; } stats.totalFiles++; const relativePath = import_path2.default.relative(cwd, id); try { const parserOptions = { sourceType: "module", plugins: ["jsx", "typescript"] }; const ast = (0, import_parser.parse)(code, parserOptions); const magicString = new import_magic_string.default(code); let changedElementsCount = 0; let currentElement = null; const threeDreiImportedElements = /* @__PURE__ */ new Set(); const threeDreiNamespaces = /* @__PURE__ */ new Set(); const { walk } = await import("estree-walker"); walk(ast, { enter(node) { if (node.type === "ImportDeclaration") { const source = node.source?.value; if (typeof source === "string" && source.includes("@react-three/drei")) { node.specifiers.forEach((spec) => { if (spec.type === "ImportSpecifier") { threeDreiImportedElements.add(spec.local.name); } else if (spec.type === "ImportNamespaceSpecifier") { threeDreiNamespaces.add(spec.local.name); } }); } } } }); walk(ast, { enter(node) { if (node.type === "JSXElement") { currentElement = node; } if (node.type === "JSXOpeningElement") { const jsxNode = node; let elementName; if (jsxNode.name.type === "JSXIdentifier") { elementName = jsxNode.name.name; } else if (jsxNode.name.type === "JSXMemberExpression") { const memberExpr = jsxNode.name; elementName = `${memberExpr.object.name}.${memberExpr.property.name}`; } else { return; } if (elementName === "Fragment" || elementName === "React.Fragment") { return; } const attributes = jsxNode.attributes.reduce((acc, attr) => { if (attr.type === "JSXAttribute") { if (attr.value?.type === "StringLiteral") { acc[attr.name.name] = attr.value.value; } else if (attr.value?.type === "JSXExpressionContainer" && attr.value.expression.type === "StringLiteral") { acc[attr.name.name] = attr.value.expression.value; } } return acc; }, {}); let textContent = ""; if (currentElement && currentElement.children) { textContent = currentElement.children.map((child) => { if (child.type === "JSXText") { return child.value.trim(); } else if (child.type === "JSXExpressionContainer") { if (child.expression.type === "StringLiteral") { return child.expression.value; } } return ""; }).filter(Boolean).join(" ").trim(); } const content = {}; if (textContent) { content.text = textContent; } if (attributes.placeholder) { content.placeholder = attributes.placeholder; } if (attributes.className) { content.className = attributes.className; } const line = jsxNode.loc?.start?.line ?? 0; const col = jsxNode.loc?.start?.column ?? 0; const dataComponentId = `${relativePath}:${line}:${col}`; const fileName = import_path2.default.basename(id); const shouldTag = shouldTagElement(elementName, threeDreiImportedElements, threeDreiNamespaces); if (shouldTag) { const legacyIds = ` data-component-path="${relativePath}" data-component-line="${line}" data-component-file="${fileName}" data-component-name="${elementName}" data-component-content="${encodeURIComponent( JSON.stringify(content) )}"`; magicString.appendLeft( jsxNode.name.end ?? 0, ` data-lov-id="${dataComponentId}" data-lov-name="${elementName}" ${legacyIds}` ); changedElementsCount++; } } } }); stats.processedFiles++; stats.totalElements += changedElementsCount; return { code: magicString.toString(), map: magicString.generateMap({ hires: true }) }; } catch (error) { console.error(`Error processing file ${relativePath}:`, error); stats.processedFiles++; return null; } }, async buildStart() { if (!isSandbox) return; try { await generateConfig(); } catch (error) { console.error("Error generating tailwind.config.lov.json:", error); } }, configureServer(server) { if (!isSandbox) return; try { server.watcher.add(tailwindInputFile); server.watcher.on("change", async (changedPath) => { if (import_path2.default.normalize(changedPath) === import_path2.default.normalize(tailwindInputFile)) { await generateConfig(); } }); } catch (error) { console.error("Error adding watcher:", error); } } }; } async function generateConfig() { try { await esbuild.build({ entryPoints: [tailwindInputFile], outfile: tailwindIntermediateFile, bundle: true, format: "esm", banner: { js: 'import { createRequire } from "module"; const require = createRequire(import.meta.url);' } }); try { const userConfig = await import( tailwindIntermediateFile + "?update=" + Date.now() // cache buster ); if (!userConfig || !userConfig.default) { console.error("Invalid Tailwind config structure:", userConfig); throw new Error("Invalid Tailwind config structure"); } const resolvedConfig = (0, import_resolveConfig.default)(userConfig.default); await import_promises.default.writeFile( tailwindJsonOutfile, JSON.stringify(resolvedConfig, null, 2) ); await import_promises.default.unlink(tailwindIntermediateFile).catch(() => { }); } catch (error) { console.error("Error processing config:", error); throw error; } } catch (error) { console.error("Error in generateConfig:", error); throw error; } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { componentTagger });