This commit is contained in:
icssoa 2025-06-23 23:45:22 +08:00 committed by icssoa
parent dabc910075
commit 609f909c75
3 changed files with 440 additions and 36 deletions

View File

@ -226,26 +226,46 @@
*/
const getDynamicClassNames = (value) => {
const names = new Set();
// 匹配数组中的字符串元素(如 'text-center'
const arrayRegex = /['"](.*?)['"]/g;
let arrayMatch;
while ((arrayMatch = arrayRegex.exec(value)) !== null) {
arrayMatch[1].trim() && names.add(arrayMatch[1]);
// 匹配函数调用中的对象参数(如 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*:/g;
// 匹配对象键(如 { 'text-a': 1 }- 优化版本,避免跨行错误匹配
const objKeyRegex = /[{,]\s*['"](.*?)['"]\s*:/gs;
let objKeyMatch;
while ((objKeyMatch = objKeyRegex.exec(value)) !== null) {
objKeyMatch[1].trim() && names.add(objKeyMatch[1]);
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*['"](.*?)['"]/g;
const ternaryRegex = /(\?|:)\s*['"](.*?)['"]/gs;
let ternaryMatch;
while ((ternaryMatch = ternaryRegex.exec(value)) !== null) {
ternaryMatch[2].trim() && names.add(ternaryMatch[2]);
}
// 匹配反引号模板字符串
const templateRegex = /`([^`]*)`/g;
// 匹配反引号模板字符串 - 改进版本
const templateRegex = /`([^`]*)`/gs;
let templateMatch;
while ((templateMatch = templateRegex.exec(value)) !== null) {
const templateContent = templateMatch[1];
@ -259,7 +279,7 @@
});
});
// 提取模板字符串中 ${} 表达式内的字符串
const expressionRegex = /\$\{([^}]*)\}/g;
const expressionRegex = /\$\{([^}]*)\}/gs;
let expressionMatch;
while ((expressionMatch = expressionRegex.exec(templateContent)) !== null) {
const expression = expressionMatch[1];
@ -267,38 +287,183 @@
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);
};
/**
* 获取类名
*/
function getClassNames(code) {
const classRegex = /(?:class|:class|:pt)\s*=\s*(['"`])([\s\S]*?)\1/gi;
// 修改正则表达式以支持多行匹配,避免内层引号冲突
const classRegex = /(?:class|:class|:pt|:hover-class)\s*=\s*(['"`])((?:[^'"`\\]|\\.|`[^`]*`|'[^']*'|"[^"]*")*?)\1/gis;
const classNames = new Set();
let match;
while ((match = classRegex.exec(code)) !== null) {
const isStaticClass = match[0].startsWith("class");
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
// 处理静态 class 和 hover-class
value.split(/\s+/).forEach((name) => name && classNames.add(name));
}
else if (isPtAttribute) {
// 处理 :pt 属性中的 className
parseClasNameFromPt(value, classNames);
}
else {
// 处理动态 :class
// 处理动态 :class 和 :hover-class
getDynamicClassNames(value).forEach((name) => classNames.add(name));
}
}
return Array.from(classNames);
}
/**
* :pt 属性中解析 className
*/
function parseClasNameFromPt(value, classNames) {
// 递归查找所有 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, startPos) {
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 内容
*/
function getClassContent(code) {
const regex = /(?:class|:class|:pt)\s*=\s*(['"`])([\s\S]*?)\1/g;
// 修改正则表达式以支持多行匹配,避免内层引号冲突
const regex = /(?:class|:class|:pt|:hover-class)\s*=\s*(['"`])((?:[^'"`\\]|\\.|`[^`]*`|'[^']*'|"[^"]*")*?)\1/gis;
const texts = [];
let match;
while ((match = regex.exec(code)) !== null) {
texts.push(match[2]);
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;
}
@ -522,6 +687,8 @@
return `type ${name}${extendsStr} = {${content}}`;
});
}
// test();
// npx ./src/uniapp-x/utils.ts
// 全局 service 对象,用于存储服务结构
const service = {};

View File

@ -26,3 +26,4 @@ export declare function isTailwindClass(className: string): boolean;
* interface type
*/
export declare function interfaceToType(code: string): string;
export declare function test(): void;

View File

@ -4,29 +4,50 @@
export const getDynamicClassNames = (value: string): string[] => {
const names = new Set<string>();
// 匹配数组中的字符串元素(如 'text-center'
const arrayRegex = /['"](.*?)['"]/g;
let arrayMatch;
while ((arrayMatch = arrayRegex.exec(value)) !== null) {
arrayMatch[1].trim() && names.add(arrayMatch[1]);
// 匹配函数调用中的对象参数(如 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*:/g;
// 匹配对象键(如 { 'text-a': 1 }- 优化版本,避免跨行错误匹配
const objKeyRegex = /[{,]\s*['"](.*?)['"]\s*:/gs;
let objKeyMatch;
while ((objKeyMatch = objKeyRegex.exec(value)) !== null) {
objKeyMatch[1].trim() && names.add(objKeyMatch[1]);
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*['"](.*?)['"]/g;
const ternaryRegex = /(\?|:)\s*['"](.*?)['"]/gs;
let ternaryMatch;
while ((ternaryMatch = ternaryRegex.exec(value)) !== null) {
ternaryMatch[2].trim() && names.add(ternaryMatch[2]);
}
// 匹配反引号模板字符串
const templateRegex = /`([^`]*)`/g;
// 匹配反引号模板字符串 - 改进版本
const templateRegex = /`([^`]*)`/gs;
let templateMatch;
while ((templateMatch = templateRegex.exec(value)) !== null) {
const templateContent = templateMatch[1];
@ -42,7 +63,7 @@ export const getDynamicClassNames = (value: string): string[] => {
});
// 提取模板字符串中 ${} 表达式内的字符串
const expressionRegex = /\$\{([^}]*)\}/g;
const expressionRegex = /\$\{([^}]*)\}/gs;
let expressionMatch;
while ((expressionMatch = expressionRegex.exec(templateContent)) !== null) {
const expression = expressionMatch[1];
@ -51,6 +72,26 @@ export const getDynamicClassNames = (value: string): string[] => {
}
}
// 处理混合字符串(模板字符串 + 普通文本),如 "`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);
};
@ -58,19 +99,26 @@ export const getDynamicClassNames = (value: string): string[] => {
*
*/
export function getClassNames(code: string): string[] {
const classRegex = /(?:class|:class|:pt)\s*=\s*(['"`])([\s\S]*?)\1/gi;
// 修改正则表达式以支持多行匹配,避免内层引号冲突
const classRegex =
/(?:class|:class|:pt|:hover-class)\s*=\s*(['"`])((?:[^'"`\\]|\\.|`[^`]*`|'[^']*'|"[^"]*")*?)\1/gis;
const classNames = new Set<string>();
let match;
while ((match = classRegex.exec(code)) !== null) {
const isStaticClass = match[0].startsWith("class");
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
// 处理静态 class 和 hover-class
value.split(/\s+/).forEach((name) => name && classNames.add(name));
} else if (isPtAttribute) {
// 处理 :pt 属性中的 className
parseClasNameFromPt(value, classNames);
} else {
// 处理动态 :class
// 处理动态 :class 和 :hover-class
getDynamicClassNames(value).forEach((name) => classNames.add(name));
}
}
@ -78,16 +126,141 @@ export function getClassNames(code: string): string[] {
return Array.from(classNames);
}
/**
* :pt className
*/
function parseClasNameFromPt(value: string, classNames: Set<string>) {
// 递归查找所有 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)\s*=\s*(['"`])([\s\S]*?)\1/g;
// 修改正则表达式以支持多行匹配,避免内层引号冲突
const regex =
/(?:class|:class|:pt|:hover-class)\s*=\s*(['"`])((?:[^'"`\\]|\\.|`[^`]*`|'[^']*'|"[^"]*")*?)\1/gis;
const texts: string[] = [];
let match;
while ((match = regex.exec(code)) !== null) {
texts.push(match[2]);
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;
@ -349,3 +522,66 @@ export function interfaceToType(code: string) {
return `type ${name}${extendsStr} = {${content}}`;
});
}
export function test() {
const html = `
<template>
<text :class="parseClass({'!bg-red-50': hoverable})"></text>
<text :class="a ? 'text-red-100' : 'text-red-200'"></text>
<text :class="\`text-red-300 \${true ? 'text-red-310' : 'text-red-320'}\`"></text>
<text class="text-red-330"></text>
<text :class="{
'text-red-400': a,
'text-red-500': b,
}"></text>
<text :class="[
'text-red-600',
'text-red-700',
{
'text-red-800': c,
}
]"></text>
<text :class="\`text-red-900\` text-red-1000"></text>
<text :pt="{
className: '!text-green-50'
}"></text>
<text :pt="{
className: parseClass({
'!text-green-100': hoverable,
}),
item: {
className: 'text-green-200'
}
}"></text>
<text :pt="{
className: \`text-green-300 \${true ? 'text-red-310' : 'text-red-320'}\`
}"></text>
<text hover-class="text-green-400"></text>
<text :hover-class="\`text-green-400\`"></text>
<text :hover-class="parseClass({'!text-green-450': hoverable})"></text>
<text :hover-class="a ? 'text-green-500' : 'text-green-600'"></text>
<text :hover-class="\`text-green-700 \${true ? 'text-red-310' : 'text-red-320'}\`"></text>
</template>
`;
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