diff --git a/packages/vite-plugin/dist/index.d.ts b/packages/vite-plugin/dist/index.d.ts index bddc16b..742068d 100644 --- a/packages/vite-plugin/dist/index.d.ts +++ b/packages/vite-plugin/dist/index.d.ts @@ -1,2 +1,24 @@ import type { Config } from "../types"; -export declare function cool(options: Config.Options): (import("vite").Plugin | Promise>)[]; +export declare function cool(options: Config.Options): (import("vite").Plugin | Promise> | { + name: string; + enforce: "pre"; + config(): { + css: { + postcss: { + plugins: { + postcssPlugin: string; + prepare(): { + Rule(rule: any): void; + Declaration(decl: any): void; + }; + }[]; + }; + }; + }; + transform(code: string, id: string): { + code: string; + map: { + mappings: string; + }; + } | null; +}[])[]; diff --git a/packages/vite-plugin/dist/index.js b/packages/vite-plugin/dist/index.js index 94b45cf..77cfd70 100644 --- a/packages/vite-plugin/dist/index.js +++ b/packages/vite-plugin/dist/index.js @@ -1,8 +1,8 @@ (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('fs'), require('path'), require('prettier'), require('axios'), require('lodash'), require('@vue/compiler-sfc'), require('magic-string'), require('glob'), require('node:util'), require('svgo')) : - typeof define === 'function' && define.amd ? define(['exports', 'fs', 'path', 'prettier', 'axios', 'lodash', '@vue/compiler-sfc', 'magic-string', 'glob', 'node:util', 'svgo'], factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.index = {}, global.fs, global.path, global.prettier, global.axios, global.lodash, global.compilerSfc, global.magicString, global.glob, global.util, global.svgo)); -})(this, (function (exports, fs, path, prettier, axios, lodash, compilerSfc, magicString, glob, util, svgo) { 'use strict'; + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('fs'), require('path'), require('prettier'), require('axios'), require('lodash'), require('@vue/compiler-sfc'), require('magic-string'), require('glob'), require('node:util'), require('svgo'), require('postcss-value-parser')) : + typeof define === 'function' && define.amd ? define(['exports', 'fs', 'path', 'prettier', 'axios', 'lodash', '@vue/compiler-sfc', 'magic-string', 'glob', 'node:util', 'svgo', 'postcss-value-parser'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.index = {}, global.fs, global.path, global.prettier, global.axios, global.lodash, global.compilerSfc, global.magicString, global.glob, global.util, global.svgo, global.valueParser)); +})(this, (function (exports, fs, path, prettier, axios, lodash, compilerSfc, magicString, glob, util, svgo, valueParser) { 'use strict'; const config = { type: "admin", @@ -50,6 +50,7 @@ function rootDir(path$1) { switch (config.type) { case "app": + case "uniapp-x": return path.join(process.env.UNI_INPUT_DIR, path$1); default: return path.join(process.cwd(), path$1); @@ -136,6 +137,7 @@ } switch (url) { case "app": + case "uniapp-x": url = "/app/base/comm/eps"; break; case "admin": @@ -319,8 +321,8 @@ } return t0; } - // 创建 Service - async function createDts() { + // 创建 Controller + async function createController() { let controller = ""; let chain = ""; // 处理数据 @@ -379,7 +381,7 @@ case "/page": res = ` { - pagination: { size: number; page: number; total: number; [key: string]: any }; + pagination: { size: number; page: number; total: number; [key: string]: any; }; list: ${en} []; [key: string]: any; } @@ -444,7 +446,7 @@ ${controller} - type Service = { + interface Service { /** * 基础请求 */ @@ -453,10 +455,7 @@ method?: "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS"; data?: any; params?: any; - headers?: { - authorization?: string; - [key: string]: any; - }, + headers?: any, timeout?: number; proxy?: boolean; [key: string]: any; @@ -469,19 +468,33 @@ `; } // 文件内容 - const text = ` - declare namespace Eps { - ${createEntity()} - ${await createDts()} - } + let text = ` + ${createEntity()} + ${await createController()} `; + // 文件名 + let name = "eps.d.ts"; + if (config.type == "uniapp-x") { + name = "eps.uts"; + text = text + .replaceAll("interface ", "export interface ") + .replaceAll("type Dict", "export type Dict") + .replaceAll("[key: string]: any;", ""); + } + else { + text = ` + declare namespace Eps { + ${text} + } + `; + } // 文本内容 const content = await formatCode(text); - const local_content = readFile(getEpsPath("eps.d.ts")); + const local_content = readFile(getEpsPath(name)); // 是否需要更新 if (content && content != local_content) { // 创建 eps 描述文件 - fs.createWriteStream(getEpsPath("eps.d.ts"), { + fs.createWriteStream(getEpsPath(name), { flags: "w", }).write(content); } @@ -541,8 +554,18 @@ } // 创建 dict async function createDict() { - const url = config.reqUrl + "/" + config.type + "/dict/info/types"; - return axios + let p = ""; + switch (config.type) { + case "app": + case "uniapp-x": + p = "/app"; + break; + case "admin": + p = "/admin"; + break; + } + const url = config.reqUrl + p + "/dict/info/types"; + const text = await axios .get(url) .then((res) => { const { code, data } = res.data; @@ -557,6 +580,7 @@ .catch(() => { error(`[cool-eps] Error:${url}`); }); + return text || ""; } // 创建 eps async function createEps() { @@ -787,7 +811,7 @@ let ctx = { serviceLang: "Node", }; - if (config.type == "app") { + if (config.type == "app" || config.type == "uniapp-x") { const manifest = readFile(rootDir("manifest.json"), true); // 文件路径 const ctxPath = rootDir("pages.json"); @@ -1018,6 +1042,339 @@ if (typeof window !== 'undefined') { }; } + // @ts-ignore + /** + * Tailwind CSS 特殊字符映射表 + * 用于将类名中的特殊字符转换为安全字符,避免编译或运行时冲突 + */ + const TAILWIND_SAFE_CHAR_MAP = { + "[": "-", + "]": "-", + "(": "-", + ")": "-", + "#": "-h-", + "!": "-i-", + "/": "-s-", + ":": "-c-", + ",": "-2c-", + }; + /** + * Tailwind CSS 常用类名前缀集合 + * 按功能分类,便于维护和扩展 + */ + const TAILWIND_CLASS_PREFIXES = [ + // 间距 + "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 = { + "--tw-text-opacity": 1, + "--tw-bg-opacity": 1, + }; + /** + * 转换类名中的特殊字符为安全字符 + * @param value 原始类名或值 + * @param isSelector 是否为选择器(true)或普通值(false) + * @returns 转换后的安全字符串 + */ + function toSafeTailwindClass(value, isSelector = false) { + // 处理任意值语法(如 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) { + 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(options) { + return { + postcssPlugin: "vite-cool-uniappx-remToRpx", + prepare() { + const handledSelectors = new Set(); + const { remUnit = 16, remPrecision = 6, rpxRatio = 2 } = options; + const factor = remUnit * rpxRatio; + return { + Rule(rule) { + 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) { + 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) => { + 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 插件 + * @param options 配置项 + * @returns Vite 插件对象 + */ + function tailwindTransformPlugin(options = {}) { + const merged = { + remUnit: 16, + remPrecision: 6, + rpxRatio: 2, + ...options, + }; + return { + name: "vite-cool-uniappx-tailwind", + enforce: "pre", + config() { + return { + css: { + postcss: { + plugins: [postcssRemToRpx(merged)], + }, + }, + }; + }, + transform(code, id) { + if (!id.includes(".uvue")) + return null; + let resultCode = code; + const tplMatch = resultCode.match(/