mirror of
https://github.com/cool-team-official/cool-admin-vue.git
synced 2025-12-11 04:22:54 +00:00
优化
This commit is contained in:
parent
b9ee6dcacf
commit
dc9bae188c
20
packages/vite-plugin/dist/index.js
vendored
20
packages/vite-plugin/dist/index.js
vendored
@ -1317,7 +1317,7 @@ if (typeof window !== 'undefined') {
|
|||||||
* Vite 插件:自动转换 .uvue 文件中的 Tailwind 类名为安全字符
|
* Vite 插件:自动转换 .uvue 文件中的 Tailwind 类名为安全字符
|
||||||
* 并自动注入 rem 转 rpx 的 PostCSS 插件
|
* 并自动注入 rem 转 rpx 的 PostCSS 插件
|
||||||
*/
|
*/
|
||||||
function tailwindTransformPlugin() {
|
function tailwindPlugin() {
|
||||||
return {
|
return {
|
||||||
name: "vite-cool-uniappx-tailwind",
|
name: "vite-cool-uniappx-tailwind",
|
||||||
enforce: "pre",
|
enforce: "pre",
|
||||||
@ -1364,18 +1364,32 @@ if (typeof window !== 'undefined') {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function codePlugin() {
|
||||||
|
return {
|
||||||
|
name: "vite-cool-uniappx-code",
|
||||||
|
transform(code, id) {
|
||||||
|
if (id.endsWith(".json")) {
|
||||||
|
return code.replace("new UTSJSONObject", "");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* uniappX 入口,自动注入 Tailwind 类名转换插件
|
* uniappX 入口,自动注入 Tailwind 类名转换插件
|
||||||
* @param options 配置项
|
* @param options 配置项
|
||||||
* @returns Vite 插件数组
|
* @returns Vite 插件数组
|
||||||
*/
|
*/
|
||||||
function uniappX() {
|
function uniappX() {
|
||||||
|
const plugins = [];
|
||||||
if (config.type == "uniapp-x") {
|
if (config.type == "uniapp-x") {
|
||||||
|
plugins.push(codePlugin());
|
||||||
if (config.tailwind.enable) {
|
if (config.tailwind.enable) {
|
||||||
return [tailwindTransformPlugin()];
|
plugins.push(tailwindPlugin());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return [];
|
return plugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cool(options) {
|
function cool(options) {
|
||||||
|
|||||||
2
packages/vite-plugin/dist/uniapp-x/code.d.ts
vendored
Normal file
2
packages/vite-plugin/dist/uniapp-x/code.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import type { Plugin } from "vite";
|
||||||
|
export declare function codePlugin(): Plugin;
|
||||||
6
packages/vite-plugin/dist/uniapp-x/tailwind.d.ts
vendored
Normal file
6
packages/vite-plugin/dist/uniapp-x/tailwind.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import type { Plugin } from "vite";
|
||||||
|
/**
|
||||||
|
* Vite 插件:自动转换 .uvue 文件中的 Tailwind 类名为安全字符
|
||||||
|
* 并自动注入 rem 转 rpx 的 PostCSS 插件
|
||||||
|
*/
|
||||||
|
export declare function tailwindPlugin(): Plugin;
|
||||||
12
packages/vite-plugin/src/uniapp-x/code.ts
Normal file
12
packages/vite-plugin/src/uniapp-x/code.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import type { Plugin } from "vite";
|
||||||
|
|
||||||
|
export function codePlugin() {
|
||||||
|
return {
|
||||||
|
name: "vite-cool-uniappx-code",
|
||||||
|
transform(code, id) {
|
||||||
|
if (id.endsWith(".json")) {
|
||||||
|
return code.replace("new UTSJSONObject", "");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
} as Plugin;
|
||||||
|
}
|
||||||
@ -1,348 +1,7 @@
|
|||||||
// @ts-ignore
|
|
||||||
import valueParser from "postcss-value-parser";
|
|
||||||
import { config } from "../config";
|
|
||||||
import type { Plugin } from "vite";
|
import type { Plugin } from "vite";
|
||||||
import { Config } from "../../types";
|
import { config } from "../config";
|
||||||
|
import { tailwindPlugin } from "./tailwind";
|
||||||
/**
|
import { codePlugin } from "./code";
|
||||||
* Tailwind CSS 特殊字符映射表
|
|
||||||
* 用于将类名中的特殊字符转换为安全字符,避免编译或运行时冲突
|
|
||||||
*/
|
|
||||||
const TAILWIND_SAFE_CHAR_MAP: Record<string, string> = {
|
|
||||||
"[": "-",
|
|
||||||
"]": "-",
|
|
||||||
"(": "-",
|
|
||||||
")": "-",
|
|
||||||
"{": "-",
|
|
||||||
"}": "-",
|
|
||||||
$: "-v-",
|
|
||||||
"#": "-h-",
|
|
||||||
"!": "-i-",
|
|
||||||
"/": "-s-",
|
|
||||||
":": "-c-",
|
|
||||||
",": "-2c-",
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tailwind CSS 常用类名前缀集合
|
|
||||||
* 按功能分类,便于维护和扩展
|
|
||||||
*/
|
|
||||||
const TAILWIND_CLASS_PREFIXES: string[] = [
|
|
||||||
// 间距
|
|
||||||
"p-",
|
|
||||||
"px-",
|
|
||||||
"py-",
|
|
||||||
"pt-",
|
|
||||||
"pr-",
|
|
||||||
"pb-",
|
|
||||||
"pl-",
|
|
||||||
"m-",
|
|
||||||
"mx-",
|
|
||||||
"my-",
|
|
||||||
"mt-",
|
|
||||||
"mr-",
|
|
||||||
"mb-",
|
|
||||||
"ml-",
|
|
||||||
"gap-",
|
|
||||||
"gap-x-",
|
|
||||||
"gap-y-",
|
|
||||||
"space-x-",
|
|
||||||
"space-y-",
|
|
||||||
"inset-",
|
|
||||||
"top-",
|
|
||||||
"right-",
|
|
||||||
"bottom-",
|
|
||||||
"left-",
|
|
||||||
|
|
||||||
// 尺寸
|
|
||||||
"w-",
|
|
||||||
"h-",
|
|
||||||
"min-w-",
|
|
||||||
"min-h-",
|
|
||||||
"max-w-",
|
|
||||||
"max-h-",
|
|
||||||
|
|
||||||
// 排版
|
|
||||||
"text-",
|
|
||||||
"font-",
|
|
||||||
"leading-",
|
|
||||||
"tracking-",
|
|
||||||
"indent-",
|
|
||||||
|
|
||||||
// 边框
|
|
||||||
"border-",
|
|
||||||
"border-t-",
|
|
||||||
"border-r-",
|
|
||||||
"border-b-",
|
|
||||||
"border-l-",
|
|
||||||
"rounded-",
|
|
||||||
"rounded-t-",
|
|
||||||
"rounded-r-",
|
|
||||||
"rounded-b-",
|
|
||||||
"rounded-l-",
|
|
||||||
"rounded-tl-",
|
|
||||||
"rounded-tr-",
|
|
||||||
"rounded-br-",
|
|
||||||
"rounded-bl-",
|
|
||||||
|
|
||||||
// 效果
|
|
||||||
"shadow-",
|
|
||||||
"blur-",
|
|
||||||
"brightness-",
|
|
||||||
"contrast-",
|
|
||||||
"drop-shadow-",
|
|
||||||
"grayscale-",
|
|
||||||
"hue-rotate-",
|
|
||||||
"invert-",
|
|
||||||
"saturate-",
|
|
||||||
"sepia-",
|
|
||||||
"backdrop-blur-",
|
|
||||||
"backdrop-brightness-",
|
|
||||||
"backdrop-contrast-",
|
|
||||||
"backdrop-grayscale-",
|
|
||||||
"backdrop-hue-rotate-",
|
|
||||||
"backdrop-invert-",
|
|
||||||
"backdrop-opacity-",
|
|
||||||
"backdrop-saturate-",
|
|
||||||
"backdrop-sepia-",
|
|
||||||
|
|
||||||
// 动画
|
|
||||||
"transition-",
|
|
||||||
"duration-",
|
|
||||||
"delay-",
|
|
||||||
"animate-",
|
|
||||||
|
|
||||||
// 变换
|
|
||||||
"translate-x-",
|
|
||||||
"translate-y-",
|
|
||||||
"rotate-",
|
|
||||||
"scale-",
|
|
||||||
"scale-x-",
|
|
||||||
"scale-y-",
|
|
||||||
"skew-x-",
|
|
||||||
"skew-y-",
|
|
||||||
"origin-",
|
|
||||||
|
|
||||||
// 布局
|
|
||||||
"columns-",
|
|
||||||
"break-after-",
|
|
||||||
"break-before-",
|
|
||||||
"break-inside-",
|
|
||||||
|
|
||||||
// Flexbox 和 Grid
|
|
||||||
"basis-",
|
|
||||||
"grow-",
|
|
||||||
"shrink-",
|
|
||||||
"grid-cols-",
|
|
||||||
"grid-rows-",
|
|
||||||
"col-span-",
|
|
||||||
"row-span-",
|
|
||||||
"col-start-",
|
|
||||||
"col-end-",
|
|
||||||
"row-start-",
|
|
||||||
"row-end-",
|
|
||||||
|
|
||||||
// SVG
|
|
||||||
"stroke-",
|
|
||||||
"stroke-w-",
|
|
||||||
"fill-",
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tailwind CSS 颜色变量映射
|
|
||||||
* 用于移除不需要的 CSS 变量声明
|
|
||||||
*/
|
|
||||||
const TAILWIND_COLOR_VARS: Record<string, number> = {
|
|
||||||
"--tw-text-opacity": 1,
|
|
||||||
"--tw-bg-opacity": 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换类名中的特殊字符为安全字符
|
|
||||||
* @param value 原始类名或值
|
|
||||||
* @param isSelector 是否为选择器(true)或普通值(false)
|
|
||||||
* @returns 转换后的安全字符串
|
|
||||||
*/
|
|
||||||
function toSafeTailwindClass(value: string, isSelector: boolean = false): string {
|
|
||||||
// 处理任意值语法(如 w-[100px])
|
|
||||||
const arbitrary = value.match(/^(.+?)-\[(.*?)\]$/);
|
|
||||||
if (arbitrary) {
|
|
||||||
if (isSelector) return value;
|
|
||||||
const [, prefix, content] = arbitrary;
|
|
||||||
const safePrefix = toSafeTailwindClass(prefix, isSelector);
|
|
||||||
const safeContent = content.replace(/[^\d.\w]/g, "-");
|
|
||||||
return `${safePrefix}-${safeContent}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let safeValue = value;
|
|
||||||
|
|
||||||
// 移除转义字符
|
|
||||||
if (safeValue.includes("\\")) {
|
|
||||||
safeValue = safeValue.replace(/\\/g, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 替换特殊字符
|
|
||||||
for (const [char, rep] of Object.entries(TAILWIND_SAFE_CHAR_MAP)) {
|
|
||||||
const reg = new RegExp("\\" + char, "g");
|
|
||||||
if (reg.test(safeValue)) {
|
|
||||||
safeValue = safeValue.replace(reg, rep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return safeValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将现代 rgb 格式(如 rgb(234 179 8 / 0.1))转换为标准 rgba 格式
|
|
||||||
* @param value rgb 字符串
|
|
||||||
* @returns 标准 rgba 字符串
|
|
||||||
*/
|
|
||||||
function rgbToRgba(value: string): string {
|
|
||||||
const match = value.match(/rgb\(([\d\s]+)\/\s*([\d.]+)\)/);
|
|
||||||
if (match) {
|
|
||||||
const [, rgb, alpha] = match;
|
|
||||||
const [r, g, b] = rgb.split(/\s+/);
|
|
||||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PostCSS 插件:将 rem 单位转换为 rpx,并处理 Tailwind 特殊字符
|
|
||||||
* @param options 配置项
|
|
||||||
* @returns PostCSS 插件对象
|
|
||||||
*/
|
|
||||||
function postcssRemToRpx() {
|
|
||||||
return {
|
|
||||||
postcssPlugin: "vite-cool-uniappx-remToRpx",
|
|
||||||
prepare() {
|
|
||||||
const handledSelectors = new Set<string>();
|
|
||||||
const { remUnit = 16, remPrecision = 6, rpxRatio = 2 } = config.tailwind;
|
|
||||||
const factor = remUnit * rpxRatio;
|
|
||||||
|
|
||||||
return {
|
|
||||||
Rule(rule: any) {
|
|
||||||
const sel = rule.selector;
|
|
||||||
if (handledSelectors.has(sel)) return;
|
|
||||||
const safeSel = toSafeTailwindClass(sel, true);
|
|
||||||
if (safeSel !== sel) {
|
|
||||||
rule.selector = safeSel;
|
|
||||||
handledSelectors.add(sel);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Declaration(decl: any) {
|
|
||||||
if (decl.value.includes("/* no-rem */")) return;
|
|
||||||
if (TAILWIND_COLOR_VARS[decl.prop]) {
|
|
||||||
decl.remove();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (decl.value.includes("rgb(") && decl.value.includes("/")) {
|
|
||||||
decl.value = rgbToRgba(decl.value);
|
|
||||||
}
|
|
||||||
if (decl.value.includes("rpx") && decl.parent.selector.includes("text-")) {
|
|
||||||
decl.prop = "font-size";
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsed = valueParser(decl.value);
|
|
||||||
let changed = false;
|
|
||||||
|
|
||||||
parsed.walk((node: any) => {
|
|
||||||
if (node.type === "word") {
|
|
||||||
// rem 转 rpx
|
|
||||||
const unit = valueParser.unit(node.value);
|
|
||||||
if (unit?.unit === "rem") {
|
|
||||||
const num = unit.number;
|
|
||||||
const precision = (num.split(".")[1] || "").length;
|
|
||||||
const rpxVal = (parseFloat(num) * factor)
|
|
||||||
.toFixed(precision || remPrecision)
|
|
||||||
.replace(/\.?0+$/, "");
|
|
||||||
node.value = `${rpxVal}rpx`;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
// 特殊字符处理
|
|
||||||
if (node.value.includes(".") || /[[\]()#!/:,]/.test(node.value)) {
|
|
||||||
const safe = toSafeTailwindClass(node.value, true);
|
|
||||||
if (safe !== node.value) {
|
|
||||||
node.value = safe;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 处理 var(--tw-xxx)
|
|
||||||
if (node.type === "function" && node.value === "var") {
|
|
||||||
if (node.nodes.length > 0 && node.nodes[0].value.startsWith("--tw-")) {
|
|
||||||
node.type = "word";
|
|
||||||
node.value = TAILWIND_COLOR_VARS[node.nodes[0].value];
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (changed) {
|
|
||||||
decl.value = parsed.toString();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
postcssRemToRpx.postcss = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vite 插件:自动转换 .uvue 文件中的 Tailwind 类名为安全字符
|
|
||||||
* 并自动注入 rem 转 rpx 的 PostCSS 插件
|
|
||||||
*/
|
|
||||||
function tailwindTransformPlugin() {
|
|
||||||
return {
|
|
||||||
name: "vite-cool-uniappx-tailwind",
|
|
||||||
enforce: "pre",
|
|
||||||
|
|
||||||
config() {
|
|
||||||
return {
|
|
||||||
css: {
|
|
||||||
postcss: {
|
|
||||||
plugins: [postcssRemToRpx()],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
transform(code, id) {
|
|
||||||
if (!id.includes(".uvue")) return null;
|
|
||||||
|
|
||||||
let resultCode = code;
|
|
||||||
const tplMatch = resultCode.match(/<template>([\s\S]*?)<\/template>/);
|
|
||||||
if (!tplMatch?.[1]) return null;
|
|
||||||
|
|
||||||
let tpl = tplMatch[1];
|
|
||||||
const tplOrigin = tpl;
|
|
||||||
|
|
||||||
TAILWIND_CLASS_PREFIXES.forEach((prefix) => {
|
|
||||||
for (const [char, rep] of Object.entries(TAILWIND_SAFE_CHAR_MAP)) {
|
|
||||||
const reg = new RegExp(`(${prefix}[^\\s'"]*?\\${char}[^\\s'"]*?)`, "g");
|
|
||||||
const matches = [...tpl.matchAll(reg)];
|
|
||||||
matches.forEach((m) => {
|
|
||||||
const raw = m[1];
|
|
||||||
const safe = raw.replace(new RegExp("\\" + char, "g"), rep);
|
|
||||||
if (process.env.NODE_ENV === "development") {
|
|
||||||
console.log(`类名转换: ${raw} → ${safe}`);
|
|
||||||
}
|
|
||||||
tpl = tpl.replace(raw, safe);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (tpl !== tplOrigin) {
|
|
||||||
resultCode = resultCode.replace(tplMatch[0], `<template>${tpl}</template>`);
|
|
||||||
return {
|
|
||||||
code: resultCode,
|
|
||||||
map: { mappings: "" },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
} as Plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* uniappX 入口,自动注入 Tailwind 类名转换插件
|
* uniappX 入口,自动注入 Tailwind 类名转换插件
|
||||||
@ -350,11 +9,15 @@ function tailwindTransformPlugin() {
|
|||||||
* @returns Vite 插件数组
|
* @returns Vite 插件数组
|
||||||
*/
|
*/
|
||||||
export function uniappX() {
|
export function uniappX() {
|
||||||
|
const plugins: Plugin[] = [];
|
||||||
|
|
||||||
if (config.type == "uniapp-x") {
|
if (config.type == "uniapp-x") {
|
||||||
|
plugins.push(codePlugin());
|
||||||
|
|
||||||
if (config.tailwind.enable) {
|
if (config.tailwind.enable) {
|
||||||
return [tailwindTransformPlugin()];
|
plugins.push(tailwindPlugin());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return plugins;
|
||||||
}
|
}
|
||||||
|
|||||||
344
packages/vite-plugin/src/uniapp-x/tailwind.ts
Normal file
344
packages/vite-plugin/src/uniapp-x/tailwind.ts
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
import valueParser from "postcss-value-parser";
|
||||||
|
import { config } from "../config";
|
||||||
|
import type { Plugin } from "vite";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tailwind CSS 特殊字符映射表
|
||||||
|
* 用于将类名中的特殊字符转换为安全字符,避免编译或运行时冲突
|
||||||
|
*/
|
||||||
|
const TAILWIND_SAFE_CHAR_MAP: Record<string, string> = {
|
||||||
|
"[": "-",
|
||||||
|
"]": "-",
|
||||||
|
"(": "-",
|
||||||
|
")": "-",
|
||||||
|
"{": "-",
|
||||||
|
"}": "-",
|
||||||
|
$: "-v-",
|
||||||
|
"#": "-h-",
|
||||||
|
"!": "-i-",
|
||||||
|
"/": "-s-",
|
||||||
|
":": "-c-",
|
||||||
|
",": "-2c-",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tailwind CSS 常用类名前缀集合
|
||||||
|
* 按功能分类,便于维护和扩展
|
||||||
|
*/
|
||||||
|
const TAILWIND_CLASS_PREFIXES: string[] = [
|
||||||
|
// 间距
|
||||||
|
"p-",
|
||||||
|
"px-",
|
||||||
|
"py-",
|
||||||
|
"pt-",
|
||||||
|
"pr-",
|
||||||
|
"pb-",
|
||||||
|
"pl-",
|
||||||
|
"m-",
|
||||||
|
"mx-",
|
||||||
|
"my-",
|
||||||
|
"mt-",
|
||||||
|
"mr-",
|
||||||
|
"mb-",
|
||||||
|
"ml-",
|
||||||
|
"gap-",
|
||||||
|
"gap-x-",
|
||||||
|
"gap-y-",
|
||||||
|
"space-x-",
|
||||||
|
"space-y-",
|
||||||
|
"inset-",
|
||||||
|
"top-",
|
||||||
|
"right-",
|
||||||
|
"bottom-",
|
||||||
|
"left-",
|
||||||
|
|
||||||
|
// 尺寸
|
||||||
|
"w-",
|
||||||
|
"h-",
|
||||||
|
"min-w-",
|
||||||
|
"min-h-",
|
||||||
|
"max-w-",
|
||||||
|
"max-h-",
|
||||||
|
|
||||||
|
// 排版
|
||||||
|
"text-",
|
||||||
|
"font-",
|
||||||
|
"leading-",
|
||||||
|
"tracking-",
|
||||||
|
"indent-",
|
||||||
|
|
||||||
|
// 边框
|
||||||
|
"border-",
|
||||||
|
"border-t-",
|
||||||
|
"border-r-",
|
||||||
|
"border-b-",
|
||||||
|
"border-l-",
|
||||||
|
"rounded-",
|
||||||
|
"rounded-t-",
|
||||||
|
"rounded-r-",
|
||||||
|
"rounded-b-",
|
||||||
|
"rounded-l-",
|
||||||
|
"rounded-tl-",
|
||||||
|
"rounded-tr-",
|
||||||
|
"rounded-br-",
|
||||||
|
"rounded-bl-",
|
||||||
|
|
||||||
|
// 效果
|
||||||
|
"shadow-",
|
||||||
|
"blur-",
|
||||||
|
"brightness-",
|
||||||
|
"contrast-",
|
||||||
|
"drop-shadow-",
|
||||||
|
"grayscale-",
|
||||||
|
"hue-rotate-",
|
||||||
|
"invert-",
|
||||||
|
"saturate-",
|
||||||
|
"sepia-",
|
||||||
|
"backdrop-blur-",
|
||||||
|
"backdrop-brightness-",
|
||||||
|
"backdrop-contrast-",
|
||||||
|
"backdrop-grayscale-",
|
||||||
|
"backdrop-hue-rotate-",
|
||||||
|
"backdrop-invert-",
|
||||||
|
"backdrop-opacity-",
|
||||||
|
"backdrop-saturate-",
|
||||||
|
"backdrop-sepia-",
|
||||||
|
|
||||||
|
// 动画
|
||||||
|
"transition-",
|
||||||
|
"duration-",
|
||||||
|
"delay-",
|
||||||
|
"animate-",
|
||||||
|
|
||||||
|
// 变换
|
||||||
|
"translate-x-",
|
||||||
|
"translate-y-",
|
||||||
|
"rotate-",
|
||||||
|
"scale-",
|
||||||
|
"scale-x-",
|
||||||
|
"scale-y-",
|
||||||
|
"skew-x-",
|
||||||
|
"skew-y-",
|
||||||
|
"origin-",
|
||||||
|
|
||||||
|
// 布局
|
||||||
|
"columns-",
|
||||||
|
"break-after-",
|
||||||
|
"break-before-",
|
||||||
|
"break-inside-",
|
||||||
|
|
||||||
|
// Flexbox 和 Grid
|
||||||
|
"basis-",
|
||||||
|
"grow-",
|
||||||
|
"shrink-",
|
||||||
|
"grid-cols-",
|
||||||
|
"grid-rows-",
|
||||||
|
"col-span-",
|
||||||
|
"row-span-",
|
||||||
|
"col-start-",
|
||||||
|
"col-end-",
|
||||||
|
"row-start-",
|
||||||
|
"row-end-",
|
||||||
|
|
||||||
|
// SVG
|
||||||
|
"stroke-",
|
||||||
|
"stroke-w-",
|
||||||
|
"fill-",
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tailwind CSS 颜色变量映射
|
||||||
|
* 用于移除不需要的 CSS 变量声明
|
||||||
|
*/
|
||||||
|
const TAILWIND_COLOR_VARS: Record<string, number> = {
|
||||||
|
"--tw-text-opacity": 1,
|
||||||
|
"--tw-bg-opacity": 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换类名中的特殊字符为安全字符
|
||||||
|
* @param value 原始类名或值
|
||||||
|
* @param isSelector 是否为选择器(true)或普通值(false)
|
||||||
|
* @returns 转换后的安全字符串
|
||||||
|
*/
|
||||||
|
function toSafeTailwindClass(value: string, isSelector: boolean = false): string {
|
||||||
|
// 处理任意值语法(如 w-[100px])
|
||||||
|
const arbitrary = value.match(/^(.+?)-\[(.*?)\]$/);
|
||||||
|
if (arbitrary) {
|
||||||
|
if (isSelector) return value;
|
||||||
|
const [, prefix, content] = arbitrary;
|
||||||
|
const safePrefix = toSafeTailwindClass(prefix, isSelector);
|
||||||
|
const safeContent = content.replace(/[^\d.\w]/g, "-");
|
||||||
|
return `${safePrefix}-${safeContent}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let safeValue = value;
|
||||||
|
|
||||||
|
// 移除转义字符
|
||||||
|
if (safeValue.includes("\\")) {
|
||||||
|
safeValue = safeValue.replace(/\\/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 替换特殊字符
|
||||||
|
for (const [char, rep] of Object.entries(TAILWIND_SAFE_CHAR_MAP)) {
|
||||||
|
const reg = new RegExp("\\" + char, "g");
|
||||||
|
if (reg.test(safeValue)) {
|
||||||
|
safeValue = safeValue.replace(reg, rep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return safeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将现代 rgb 格式(如 rgb(234 179 8 / 0.1))转换为标准 rgba 格式
|
||||||
|
* @param value rgb 字符串
|
||||||
|
* @returns 标准 rgba 字符串
|
||||||
|
*/
|
||||||
|
function rgbToRgba(value: string): string {
|
||||||
|
const match = value.match(/rgb\(([\d\s]+)\/\s*([\d.]+)\)/);
|
||||||
|
if (match) {
|
||||||
|
const [, rgb, alpha] = match;
|
||||||
|
const [r, g, b] = rgb.split(/\s+/);
|
||||||
|
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostCSS 插件:将 rem 单位转换为 rpx,并处理 Tailwind 特殊字符
|
||||||
|
* @param options 配置项
|
||||||
|
* @returns PostCSS 插件对象
|
||||||
|
*/
|
||||||
|
function postcssRemToRpx() {
|
||||||
|
return {
|
||||||
|
postcssPlugin: "vite-cool-uniappx-remToRpx",
|
||||||
|
prepare() {
|
||||||
|
const handledSelectors = new Set<string>();
|
||||||
|
const { remUnit = 16, remPrecision = 6, rpxRatio = 2 } = config.tailwind;
|
||||||
|
const factor = remUnit * rpxRatio;
|
||||||
|
|
||||||
|
return {
|
||||||
|
Rule(rule: any) {
|
||||||
|
const sel = rule.selector;
|
||||||
|
if (handledSelectors.has(sel)) return;
|
||||||
|
const safeSel = toSafeTailwindClass(sel, true);
|
||||||
|
if (safeSel !== sel) {
|
||||||
|
rule.selector = safeSel;
|
||||||
|
handledSelectors.add(sel);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Declaration(decl: any) {
|
||||||
|
if (decl.value.includes("/* no-rem */")) return;
|
||||||
|
if (TAILWIND_COLOR_VARS[decl.prop]) {
|
||||||
|
decl.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (decl.value.includes("rgb(") && decl.value.includes("/")) {
|
||||||
|
decl.value = rgbToRgba(decl.value);
|
||||||
|
}
|
||||||
|
if (decl.value.includes("rpx") && decl.parent.selector.includes("text-")) {
|
||||||
|
decl.prop = "font-size";
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = valueParser(decl.value);
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
|
parsed.walk((node: any) => {
|
||||||
|
if (node.type === "word") {
|
||||||
|
// rem 转 rpx
|
||||||
|
const unit = valueParser.unit(node.value);
|
||||||
|
if (unit?.unit === "rem") {
|
||||||
|
const num = unit.number;
|
||||||
|
const precision = (num.split(".")[1] || "").length;
|
||||||
|
const rpxVal = (parseFloat(num) * factor)
|
||||||
|
.toFixed(precision || remPrecision)
|
||||||
|
.replace(/\.?0+$/, "");
|
||||||
|
node.value = `${rpxVal}rpx`;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
// 特殊字符处理
|
||||||
|
if (node.value.includes(".") || /[[\]()#!/:,]/.test(node.value)) {
|
||||||
|
const safe = toSafeTailwindClass(node.value, true);
|
||||||
|
if (safe !== node.value) {
|
||||||
|
node.value = safe;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 处理 var(--tw-xxx)
|
||||||
|
if (node.type === "function" && node.value === "var") {
|
||||||
|
if (node.nodes.length > 0 && node.nodes[0].value.startsWith("--tw-")) {
|
||||||
|
node.type = "word";
|
||||||
|
node.value = TAILWIND_COLOR_VARS[node.nodes[0].value];
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
decl.value = parsed.toString();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
postcssRemToRpx.postcss = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vite 插件:自动转换 .uvue 文件中的 Tailwind 类名为安全字符
|
||||||
|
* 并自动注入 rem 转 rpx 的 PostCSS 插件
|
||||||
|
*/
|
||||||
|
export function tailwindPlugin() {
|
||||||
|
return {
|
||||||
|
name: "vite-cool-uniappx-tailwind",
|
||||||
|
enforce: "pre",
|
||||||
|
|
||||||
|
config() {
|
||||||
|
return {
|
||||||
|
css: {
|
||||||
|
postcss: {
|
||||||
|
plugins: [postcssRemToRpx()],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
transform(code, id) {
|
||||||
|
if (!id.includes(".uvue")) return null;
|
||||||
|
|
||||||
|
let resultCode = code;
|
||||||
|
const tplMatch = resultCode.match(/<template>([\s\S]*?)<\/template>/);
|
||||||
|
if (!tplMatch?.[1]) return null;
|
||||||
|
|
||||||
|
let tpl = tplMatch[1];
|
||||||
|
const tplOrigin = tpl;
|
||||||
|
|
||||||
|
TAILWIND_CLASS_PREFIXES.forEach((prefix) => {
|
||||||
|
for (const [char, rep] of Object.entries(TAILWIND_SAFE_CHAR_MAP)) {
|
||||||
|
const reg = new RegExp(`(${prefix}[^\\s'"]*?\\${char}[^\\s'"]*?)`, "g");
|
||||||
|
const matches = [...tpl.matchAll(reg)];
|
||||||
|
matches.forEach((m) => {
|
||||||
|
const raw = m[1];
|
||||||
|
const safe = raw.replace(new RegExp("\\" + char, "g"), rep);
|
||||||
|
if (process.env.NODE_ENV === "development") {
|
||||||
|
console.log(`类名转换: ${raw} → ${safe}`);
|
||||||
|
}
|
||||||
|
tpl = tpl.replace(raw, safe);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (tpl !== tplOrigin) {
|
||||||
|
resultCode = resultCode.replace(tplMatch[0], `<template>${tpl}</template>`);
|
||||||
|
return {
|
||||||
|
code: resultCode,
|
||||||
|
map: { mappings: "" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
} as Plugin;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user