/** * 获取动态类名 */ export const getDynamicClassNames = (value: string): string[] => { const names = new Set(); // 匹配函数调用中的对象参数(如 parseClass({'!bg-surface-50': hoverable})) const functionCallRegex = /\w+\s*\(\s*\{([^}]*)\}\s*\)/gs; let funcMatch; while ((funcMatch = functionCallRegex.exec(value)) !== null) { const objContent = funcMatch[1]; // 提取对象中的键 const keyRegex = /['"](.*?)['"]\s*:/gs; let keyMatch; while ((keyMatch = keyRegex.exec(objContent)) !== null) { keyMatch[1].trim() && names.add(keyMatch[1]); } } // 匹配对象键(如 { 'text-a': 1 })- 优化版本,避免跨行错误匹配 const objKeyRegex = /[{,]\s*['"](.*?)['"]\s*:/gs; let objKeyMatch; while ((objKeyMatch = objKeyRegex.exec(value)) !== null) { const className = objKeyMatch[1].trim(); // 确保是有效的CSS类名,避免匹配到错误内容 if (className && !className.includes("\n") && !className.includes("\t")) { names.add(className); } } // 匹配数组中的字符串元素(如 'text-center')- 优化版本 const arrayStringRegex = /(?:^|[,\[\s])\s*['"](.*?)['"]/gs; let arrayMatch; while ((arrayMatch = arrayStringRegex.exec(value)) !== null) { const className = arrayMatch[1].trim(); // 确保是有效的CSS类名 if (className && !className.includes("\n") && !className.includes("\t")) { names.add(className); } } // 匹配三元表达式中的字符串(如 'dark' 和 'light') const ternaryRegex = /(\?|:)\s*['"](.*?)['"]/gs; let ternaryMatch; while ((ternaryMatch = ternaryRegex.exec(value)) !== null) { ternaryMatch[2].trim() && names.add(ternaryMatch[2]); } // 匹配反引号模板字符串 - 改进版本 const templateRegex = /`([^`]*)`/gs; let templateMatch; while ((templateMatch = templateRegex.exec(value)) !== null) { const templateContent = templateMatch[1]; // 提取模板字符串中的普通文本部分(排除 ${} 表达式) const textParts = templateContent.split(/\$\{[^}]*\}/); textParts.forEach((part) => { part.trim() .split(/\s+/) .forEach((className) => { className.trim() && names.add(className.trim()); }); }); // 提取模板字符串中 ${} 表达式内的字符串 const expressionRegex = /\$\{([^}]*)\}/gs; let expressionMatch; while ((expressionMatch = expressionRegex.exec(templateContent)) !== null) { const expression = expressionMatch[1]; // 递归处理表达式中的动态类名 getDynamicClassNames(expression).forEach((name) => names.add(name)); } } // 处理混合字符串(模板字符串 + 普通文本),如 "`text-red-900` text-red-1000" const mixedStringRegex = /`[^`]*`\s+([a-zA-Z0-9\-_\s]+)/g; let mixedMatch; while ((mixedMatch = mixedStringRegex.exec(value)) !== null) { const additionalClasses = mixedMatch[1].trim().split(/\s+/); additionalClasses.forEach((className) => { className.trim() && names.add(className.trim()); }); } // 处理普通字符串,多个类名用空格分割 const stringRegex = /['"]([\w\s\-!:\/]+?)['"]/gs; let stringMatch; while ((stringMatch = stringRegex.exec(value)) !== null) { const classNames = stringMatch[1].trim().split(/\s+/); classNames.forEach((className) => { className.trim() && names.add(className.trim()); }); } return Array.from(names); }; /** * 获取类名 */ export function getClassNames(code: string): string[] { // 修改正则表达式以支持多行匹配,避免内层引号冲突 const classRegex = /(?:class|:class|:pt|:hover-class)\s*=\s*(['"`])((?:[^'"`\\]|\\.|`[^`]*`|'[^']*'|"[^"]*")*?)\1/gis; const classNames = new Set(); let match; while ((match = classRegex.exec(code)) !== null) { const attribute = match[0].split("=")[0].trim(); const isStaticClass = attribute === "class" || attribute === "hover-class"; const isPtAttribute = attribute.includes("pt"); const value = match[2].trim(); if (isStaticClass) { // 处理静态 class 和 hover-class value.split(/\s+/).forEach((name) => name && classNames.add(name)); } else if (isPtAttribute) { // 处理 :pt 属性中的 className parseClasNameFromPt(value, classNames); } else { // 处理动态 :class 和 :hover-class getDynamicClassNames(value).forEach((name) => classNames.add(name)); } } return Array.from(classNames); } /** * 从 :pt 属性中解析 className */ function parseClasNameFromPt(value: string, classNames: Set) { // 递归查找所有 className 属性 const classNameRegex = /className\s*:\s*/g; let match; while ((match = classNameRegex.exec(value)) !== null) { const startPos = match.index + match[0].length; const classNameValue = extractComplexValue(value, startPos); if (classNameValue) { // 如果是字符串字面量 if ( classNameValue.startsWith('"') || classNameValue.startsWith("'") || classNameValue.startsWith("`") ) { if (classNameValue.startsWith("`")) { // 处理模板字符串 getDynamicClassNames(classNameValue).forEach((name) => classNames.add(name)); } else { // 处理普通字符串 const strMatch = classNameValue.match(/['"](.*?)['"]/); if (strMatch) { strMatch[1].split(/\s+/).forEach((name) => name && classNames.add(name)); } } } else { // 处理动态值(如函数调用、对象等) getDynamicClassNames(classNameValue).forEach((name) => classNames.add(name)); } } } } /** * 提取复杂值(支持嵌套引号和括号) */ function extractComplexValue(text: string, startPos: number): string | null { let pos = startPos; let depth = 0; let inString = false; let stringChar = ""; let result = ""; // 跳过开头的空白字符 while (pos < text.length && /\s/.test(text[pos])) { pos++; } while (pos < text.length) { const char = text[pos]; if (!inString) { if (char === '"' || char === "'" || char === "`") { inString = true; stringChar = char; result += char; } else if (char === "{" || char === "(" || char === "[") { depth++; result += char; } else if (char === "}" || char === ")" || char === "]") { if (depth === 0 && char === "}") { // 遇到顶层的 } 时结束 break; } depth--; result += char; } else if (char === "," && depth === 0) { // 遇到顶层的逗号时结束 break; } else if (char === "\n" && depth === 0 && result.trim() !== "") { // 如果遇到换行且不在嵌套结构中,且已有内容,则结束 break; } else { result += char; } } else { result += char; if (char === stringChar && text[pos - 1] !== "\\") { inString = false; stringChar = ""; // 如果字符串结束且depth为0,检查是否应该结束 if (depth === 0) { // 看看下一个非空白字符是什么 let nextPos = pos + 1; while (nextPos < text.length && /\s/.test(text[nextPos])) { nextPos++; } if (nextPos < text.length && (text[nextPos] === "," || text[nextPos] === "}")) { // 如果下一个字符是逗号或右括号,则结束 break; } } } } pos++; } return result.trim() || null; } /** * 获取 class 内容 */ export function getClassContent(code: string) { // 修改正则表达式以支持多行匹配,避免内层引号冲突 const regex = /(?:class|:class|:pt|:hover-class)\s*=\s*(['"`])((?:[^'"`\\]|\\.|`[^`]*`|'[^']*'|"[^"]*")*?)\1/gis; const texts: string[] = []; let match; while ((match = regex.exec(code)) !== null) { const attribute = match[0].split("=")[0].trim(); const isPtAttribute = attribute.includes("pt"); const value = match[2]; if (isPtAttribute) { // 手动解析 className 值 const classNameRegex = /className\s*:\s*/g; let classNameMatchResult; while ((classNameMatchResult = classNameRegex.exec(value)) !== null) { const startPos = classNameMatchResult.index + classNameMatchResult[0].length; const classNameValue = extractComplexValue(value, startPos); if (classNameValue) { texts.push(classNameValue); } } } else { texts.push(value); } } return texts; } /** * 获取节点 */ export function getNodes(code: string) { const nodes: string[] = []; // 找到所有顶级template标签的完整内容 function findTemplateContents(content: string): string[] { const results: string[] = []; let index = 0; while (index < content.length) { const templateStart = content.indexOf("", templateStart); if (tagEnd === -1) break; // 使用栈来匹配配对的template标签 let stack = 1; let currentPos = tagEnd + 1; while (currentPos < content.length && stack > 0) { const nextTemplateStart = content.indexOf("", currentPos); if (nextTemplateEnd === -1) break; // 如果开始标签更近,说明有嵌套 if (nextTemplateStart !== -1 && nextTemplateStart < nextTemplateEnd) { // 找到开始标签的完整结束 const nestedTagEnd = content.indexOf(">", nextTemplateStart); if (nestedTagEnd !== -1) { stack++; currentPos = nestedTagEnd + 1; } else { break; } } else { // 找到结束标签 stack--; currentPos = nextTemplateEnd + 11; // ''.length } } if (stack === 0) { // 提取template内容(不包括template标签本身) const templateContent = content.substring(tagEnd + 1, currentPos - 11); results.push(templateContent); index = currentPos; } else { // 如果没有找到匹配的结束标签,跳过这个开始标签 index = tagEnd + 1; } } return results; } // 递归提取所有template内容中的节点 function extractNodesFromContent(content: string): void { // 先提取当前内容中的所有标签 const regex = /<([^>]+)>/g; let match; while ((match = regex.exec(content)) !== null) { if (!match[1].startsWith("/") && !match[1].startsWith("template")) { nodes.push(match[1]); } } // 递归处理嵌套的template const nestedTemplates = findTemplateContents(content); nestedTemplates.forEach((templateContent) => { extractNodesFromContent(templateContent); }); } // 获取所有顶级template内容 const templateContents = findTemplateContents(code); // 处理每个template内容 templateContents.forEach((templateContent) => { extractNodesFromContent(templateContent); }); return nodes.map((e) => `<${e}>`); } /** * 添加 script 标签内容 */ export function addScriptContent(code: string, content: string) { const scriptMatch = /]*>([\s\S]*?)<\/script>/g.exec(code); if (!scriptMatch) { return code; } const scriptContent = scriptMatch[1]; const scriptStartIndex = scriptMatch.index + scriptMatch[0].indexOf(">") + 1; const scriptEndIndex = scriptStartIndex + scriptContent.length; return ( code.substring(0, scriptStartIndex) + "\n" + content + "\n" + scriptContent.trim() + code.substring(scriptEndIndex) ); } /** * 判断是否为 Tailwind 类名 */ export function isTailwindClass(className: string): boolean { const prefixes = [ // 布局 "container", "flex", "grid", "block", "inline", "hidden", "visible", // 间距 "p-", "px-", "py-", "pt-", "pr-", "pb-", "pl-", "m-", "mx-", "my-", "mt-", "mr-", "mb-", "ml-", "space-", "gap-", // 尺寸 "w-", "h-", "min-w-", "max-w-", "min-h-", "max-h-", // 颜色 "bg-", "text-", "border-", "ring-", "shadow-", // 边框 "border", "rounded", "ring", // 字体 "font-", "text-", "leading-", "tracking-", "antialiased", // 定位 "absolute", "relative", "fixed", "sticky", "static", "top-", "right-", "bottom-", "left-", "inset-", "z-", // 变换 "transform", "translate-", "rotate-", "scale-", "skew-", // 过渡 "transition", "duration-", "ease-", "delay-", // 交互 "cursor-", "select-", "pointer-events-", // 溢出 "overflow-", "truncate", // 滚动 "scroll-", // 伪类和响应式 "hover:", "focus:", "active:", "disabled:", "group-hover:", ]; const statePrefixes = ["dark:", "light:", "sm:", "md:", "lg:", "xl:", "2xl:"]; if (className.startsWith("!") && !className.includes("!=")) { return true; } for (const prefix of prefixes) { if (className.startsWith(prefix)) { return true; } for (const statePrefix of statePrefixes) { if (className.startsWith(statePrefix + prefix)) { return true; } } } return false; } /** * 将 interface 转换为 type */ export function interfaceToType(code: string) { // 匹配 interface 定义 const interfaceRegex = /interface\s+(\w+)(\s*extends\s+\w+)?\s*\{([^}]*)\}/g; // 将 interface 转换为 type return code.replace(interfaceRegex, (match, name, extends_, content) => { // 处理可能存在的 extends const extendsStr = extends_ ? extends_ : ""; // 返回转换后的 type 定义 return `type ${name}${extendsStr} = {${content}}`; }); } export function test() { const html = ` `; const nodes = getNodes(html); console.log("所有节点:"); nodes.forEach((node, index) => { console.log(`第${index + 1}个节点:`, node); }); console.log("\n详细分析:"); nodes.forEach((node, index) => { const classContents = getClassContent(node); const classNames = getClassNames(node); console.log(`第${index + 1}个节点:`); console.log("classContents", classContents); console.log("classNames", classNames); console.log("---"); }); } // test(); // npx ./src/uniapp-x/utils.ts