UNPKG

transform-to-unocss

Version:

🚀 Effortlessly transform CSS, inline styles, and preprocessors (Sass/Less/Stylus) to UnoCSS with smart conflict resolution and debug support

1,105 lines (1,085 loc) 41.1 kB
"use strict"; //#region rolldown:runtime 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 __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: ((k) => from[k]).bind(null, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); //#endregion const node_fs = __toESM(require("node:fs")); const node_path = __toESM(require("node:path")); const node_process = __toESM(require("node:process")); const __unocss_core = __toESM(require("@unocss/core")); const __unocss_preset_uno = __toESM(require("@unocss/preset-uno")); const node_url = __toESM(require("node:url")); const node_fs_promises = __toESM(require("node:fs/promises")); const transform_to_unocss_core = __toESM(require("transform-to-unocss-core")); const node_html_parser = __toESM(require("node-html-parser")); const __babel_core = __toESM(require("@babel/core")); const __vue_babel_plugin_jsx = __toESM(require("@vue/babel-plugin-jsx")); //#region src/utils.ts const TRANSFER_FLAG = ".__unocss_transfer__"; function transformUnocssBack(code) { const result = []; return new Promise((resolve) => { (0, __unocss_core.createGenerator)({}, { presets: [(0, __unocss_preset_uno.default)()] }).generate(code || "").then((res) => { const css = res.getLayers(); code.forEach((item) => { try { const reg = new RegExp(`${item.replace(/([!()[\]*])/g, "\\\\$1")}{(.*)}`); const match = css.match(reg); if (!match) return; const matcher = match[1]; matcher.split(";").filter(Boolean).forEach((item$1) => { const [key, v] = item$1.split(":"); result.push(key.trim()); }); } catch (error) {} }); resolve(result); }); }); } function diffTemplateStyle(before, after) { const s1 = before.match(/<style scoped>.*<\/style>/s); const s2 = after.match(/<style scoped>.*<\/style>/s); if (!s1 || !s2) return false; return s1[0] === s2[0]; } function isEmptyStyle(code) { return /<style scoped>\s*<\/style>/.test(code); } function getStyleScoped(code) { const match = code.match(/<style scoped>(.*)<\/style>/s); if (!match) return ""; return match[1]; } function getCssType(filename) { const ext = filename.split(".").pop(); const result = ext === "styl" ? "stylus" : ext; return result; } /** * 动态导入 Vue Compiler SFC,避免打包时的问题 * @returns Vue Compiler SFC 中的方法 */ async function getVueCompilerSfc() { const { parse: parse$1 } = await import("@vue/compiler-sfc"); return { parse: parse$1 }; } //#endregion //#region src/prettierCode.ts const emptyStyle = /<style[\s\w'=]*>(\s+)/; async function prettierCode(code) { const { parse: parse$1 } = await getVueCompilerSfc(); const { descriptor: { styles } } = parse$1(code); if (!styles.length) return code.replace(emptyStyle, (all, v) => all.replace(v, "")).replace(/\n+/g, "\n"); const { content } = styles[0]; return code.replace(content, removeEmptyStyle(content.replace(/\n+/g, "\n"))); } function removeEmptyStyle(code) { return code.replace(/([\w.#:[\]="'\-\s,>~]+)\{\s*\}/g, ""); } //#endregion //#region src/lessCompiler.ts async function lessCompiler(css, filepath, globalCss, alias) { if (typeof window !== "undefined") throw new Error("lessCompiler is not supported in this browser"); const { LessPluginModuleResolver } = await import("less-plugin-module-resolver"); let result = globalCss ? `${globalCss.replace(/@(?:include|import)\s+["']([^"']*)['"]/g, (_, v) => _.replace(v, node_url.pathToFileURL(node_path.default.resolve(node_process.default.cwd(), v))))}${css}` : css; try { result = (await (await import("less")).default.render(result, { filename: filepath, plugins: [new LessPluginModuleResolver({ alias: alias || {} })] })).css; return result; } catch (error) { console.error(`Error:\n transform-to-unocss(lessCompiler) ${error.toString()}`); } } //#endregion //#region src/sassCompiler.ts async function sassCompiler(css, filepath, globalCss) { if (typeof window !== "undefined") throw new Error("sassCompiler is not supported in this browser"); const baseDir = filepath ? node_path.default.dirname(filepath) : node_process.default.cwd(); const processPathInContent = (content) => { let processed = content; processed = processed.replace(/@(?:use|forward|include|import)\s+["']([^"']*)['"]/g, (match, filePath) => { if (filePath.includes(":")) return match; const resolvedPath = node_path.default.resolve(baseDir, filePath); return match.replace(filePath, node_url.pathToFileURL(resolvedPath)); }); processed = processed.replace(/@use\s+["']([^"']*)['"]\s+(?:as\s+\w+\*?|with\s*\([^)]*\))/g, (match, filePath) => { if (filePath.includes(":")) return match; const resolvedPath = node_path.default.resolve(baseDir, filePath); return match.replace(filePath, node_url.pathToFileURL(resolvedPath)); }); processed = processed.replace(/@forward\s+["']([^"']*)['"]\s*(?:show\s+[^;]+|hide\s+[^;]+|as\s+[^;]+)/g, (match, filePath) => { if (filePath.includes(":")) return match; const resolvedPath = node_path.default.resolve(baseDir, filePath); return match.replace(filePath, node_url.pathToFileURL(resolvedPath)); }); return processed; }; let result = ""; if (globalCss) result += processPathInContent(globalCss); result += processPathInContent(css); try { const sass = await import("sass"); result = sass.compileString(result, filepath ? { importers: [{ findFileUrl(url) { if (url.startsWith("~")) return new URL(url.slice(1), node_url.pathToFileURL(baseDir)); if (!url.startsWith("/") && !url.includes("://")) return new URL(url, node_url.pathToFileURL(baseDir)); return new URL(url, node_url.pathToFileURL(baseDir)); } }], syntax: "scss", loadPaths: [baseDir, node_process.default.cwd()] } : { syntax: "scss", loadPaths: [node_process.default.cwd()] }).css; return result; } catch (error) { console.error(`Error:\n transform-to-unocss(sassCompiler) ${error.toString()}`); } } //#endregion //#region src/stylusCompiler.ts async function stylusCompiler(css, filepath, globalCss) { if (typeof window !== "undefined") throw new Error("Stylus is not supported in this browser"); let result = globalCss ? `${globalCss.replace(/@(?:include|import)\s+["']([^"']*)['"]/g, (_, v) => _.replace(v, node_url.pathToFileURL(node_path.default.resolve(node_process.default.cwd(), v))))}${css}` : css; try { result = (await import("stylus")).default.render(result, { filename: filepath }); return result; } catch (error) { console.error(`Error:\n transform-to-unocss(stylusCompiler) ${error.toString()}`); } } //#endregion //#region src/compilerCss.ts /** * Compiles CSS from different preprocessors (Sass, Less, Stylus) to standard CSS * @param css - The CSS/preprocessor code to compile * @param lang - The language type ('css' | 'scss' | 'less' | 'styl') * @param filepath - The file path for resolving imports and relative paths. * In plugin mode, this should always be provided by the build tool. * @param globalCss - Global CSS configuration for preprocessors * @returns Compiled CSS string or original CSS if no compilation needed */ function compilerCss(css, lang, filepath, globalCss) { switch (lang) { case "styl": var _globalCss$styl; return stylusCompiler(css, filepath, globalCss === null || globalCss === void 0 || (_globalCss$styl = globalCss.styl) === null || _globalCss$styl === void 0 ? void 0 : _globalCss$styl.additionalData); case "less": var _globalCss$less; return lessCompiler(css, filepath, globalCss === null || globalCss === void 0 || (_globalCss$less = globalCss.less) === null || _globalCss$less === void 0 ? void 0 : _globalCss$less.additionalData); case "scss": var _globalCss$scss; return sassCompiler(css, filepath, globalCss === null || globalCss === void 0 || (_globalCss$scss = globalCss.scss) === null || _globalCss$scss === void 0 ? void 0 : _globalCss$scss.additionalData); default: return css; } } //#endregion //#region src/node-html-parser.ts function nodeHtmlParser(code, selector, stack) { const results = []; if (!stack || !stack.length) return results; const root = (0, node_html_parser.parse)(code); let elements = []; try { elements = root.querySelectorAll(selector); } catch (error) {} while (elements.length === 0 && selector.includes(":")) { const index = selector.lastIndexOf(":"); selector = selector.slice(0, index); try { elements = root.querySelectorAll(selector); } catch (error) {} } if (elements.length) elements.forEach((element) => { const targetNode = getMatchNode(element.range, stack); if (targetNode) results.push(targetNode); }); return results; } function getMatchNode(range, elements) { for (const element of elements) { const { start, end } = element.loc; if (range[0] === start.offset && range[1] === end.offset) return element; else if (element.children && element.children.length) { const match = getMatchNode(range, element.children); if (match) return match; } } } //#endregion //#region src/tail.ts function tail(css) { if (/not\(/.test(css)) return `[&:${css}]:`; if (css.startsWith("nth")) { if (css === "nth-child(odd)") return "odd"; if (css === "nth-child(even)") return "even"; if (css.startsWith("nth-child(")) { const match = css.match(/nth-child\((.*)\)/); if (match && match[1]) return `nth-[${match[1]}]`; return ""; } if (css.startsWith("nth-last-child(")) { const match = css.match(/nth-last-child\((.*)\)/); if (match && match[1]) return `nth-last-[${match[1]}]`; return ""; } if (!css.includes("(")) return css; return css.split("(")[0]; } if (css.startsWith("aria-") || css.startsWith("data-")) return css.split("=")[0]; if (css.startsWith("dir=")) return css.split("=")[1]; if (css === "file-selector-button") return "file"; if (css.endsWith("-child")) return css.split("-")[0]; if (css.startsWith("has(")) { const match = css.match(/has\((.*)\)/); if (match && match[1]) return `has-[${match[1]}]`; return ""; } if (css.startsWith("where(")) { const match = css.match(/where\((.*)\)/); if (match && match[1]) return `in-[${match[1]}]`; return ""; } if ([ "first-child", "last-child", "only-child" ].includes(css)) return css.split("-")[0]; const [first, ...rest] = css.split(":"); if (rest.length) return `[&:${(0, transform_to_unocss_core.joinWithUnderLine)(first)}]:${rest.join(":")}`; return `[&:${(0, transform_to_unocss_core.joinWithUnderLine)(first)}]`; } //#endregion //#region src/wrapperVueTemplate.ts function wrapperVueTemplate(template, css) { let _template = /<template>/.test(template) ? template.replace(/\n+/g, "\n") : `<template> ${template} </template>`; _template = /<style scoped>/.test(_template) ? _template.replace("</style>", `\n${css}<\/style>`) : `${_template}\n<style scoped> ${css} </style>`; return `${_template}`; } //#endregion //#region src/transformCss.ts const tailReg = /:?:(.+)/; const emptyClass = /[,\w>.#\-+:[\]="'\s()]+\{\}\n/g; let isRem; async function transformCss(style, code, media = "", isJsx = true, filepath, _isRem, debug = false, globalCss) { var _parse$descriptor$tem; isRem = _isRem; const allChanges = []; const { parse: parse$1 } = await getVueCompilerSfc(); let newCode = await importCss(code, style, filepath, isJsx, debug, globalCss); if (debug) console.log("[DEBUG] transformCss started:", JSON.stringify({ filepath, media, isJsx, styleLength: style.length, codeLength: code.length }, null, 2)); const stack = (_parse$descriptor$tem = parse$1(newCode).descriptor.template) === null || _parse$descriptor$tem === void 0 ? void 0 : _parse$descriptor$tem.ast; const updateOffsetMap = {}; const deferRun = []; style.replace(/(.*)\{([#\\\s\w\-.:;,%()+'"!]*)\}/g, (all, name, value = "") => { name = (0, transform_to_unocss_core.trim)(name.replace(/\s+/g, " ")); const originClassName = name; const before = (0, transform_to_unocss_core.trim)(value.replace(/\n\s*/g, "")); if (debug) console.log("[DEBUG] Processing CSS rule:", JSON.stringify({ originClassName, before, all }, null, 2)); const [transfer, noTransfer] = (0, transform_to_unocss_core.transformStyleToUnocss)(before, isRem); if (debug) console.log("[DEBUG] CSS transform result:", JSON.stringify({ originClassName, before, transfer, noTransfer: (noTransfer === null || noTransfer === void 0 ? void 0 : noTransfer.length) || 0 }, null, 2)); const tailMatcher = name.match(tailReg); const prefix = tailMatcher ? (name.endsWith(tailMatcher[0]) ? "" : "group-") + tail(tailMatcher[1]) : ""; if (prefix === "group-deep") return; if (prefix.includes(" ")) return; const after = prefix && transfer ? `${prefix}="${transfer.replace(/="\[([^\]]*)\]"/g, (_, v) => `-[${v}]`)}"` : transfer ?? before; if (before === after) { if (debug) console.log("[DEBUG] CSS rule skipped - no transformation:", JSON.stringify({ originClassName, before }, null, 2)); return; } if (prefix) name = name.replace(tailMatcher[0], ""); const result = nodeHtmlParser(newCode, originClassName, stack === null || stack === void 0 ? void 0 : stack.children); if (!result.length) { if (debug) console.log("[DEBUG] No HTML elements found for CSS rule:", JSON.stringify({ originClassName, name }, null, 2)); return; } if (debug) console.log("[DEBUG] Found HTML elements for CSS rule:", JSON.stringify({ originClassName, elementsCount: result.length, elements: result.map((r) => ({ tag: r.tag, start: r.loc.start.offset })) }, null, 2)); const _class = newCode.match(/<style[^>]+>(.*)<\/style>/s)[1]; let newClass = _class.replace(all, (_) => _.replace(value, noTransfer.join(";"))); newClass = newClass.replace(emptyClass, ""); newCode = newCode.replace(_class, newClass); for (const r of result) { const parent = r.parent; if (prefix.startsWith("group-") && parent) { const hasClass = parent.props.find((i) => i.name === "class"); if (hasClass) { if (hasClass.value.content.includes("group")) return; const index = hasClass.value.loc.start.offset; const newIndex = hasClass.value.loc.start.offset + getCalculateOffset(updateOffsetMap, index); const updateText = "group "; updateOffsetMap[index] = updateText.length; hasClass.value.content = `${hasClass.value.content} ${updateText}`; newCode = `${newCode.slice(0, newIndex + 1)}${updateText}${newCode.slice(newIndex + 1)}`; } else { const index = parent.loc.start.offset + parent.tag.length + 1; const newIndex = hasClass.value.loc.start.offset + getCalculateOffset(updateOffsetMap, index); const updateText = "class=\"group\" "; parent.props.push({ type: 6, name: "class", value: { type: 2, content: "group", loc: { start: { column: 0, line: 0, offset: newIndex }, end: { column: 0, line: 0, offset: newIndex + updateText.length } } }, loc: { start: { column: 0, line: 0, offset: newIndex }, end: { column: 0, line: 0, offset: newIndex + updateText.length } } }); updateOffsetMap[index] = updateText.length; newCode = `${newCode.slice(0, newIndex)}${updateText}${newCode.slice(newIndex)}`; } } const { loc: { start, end }, tag, props } = r; const attr = props.reduce((result$1, cur) => { var _cur$value; let item; if (cur.name === "class" && (item = (_cur$value = cur.value) === null || _cur$value === void 0 ? void 0 : _cur$value.content)) result$1.push(item); else if (!cur.value) result$1.push(cur.name); return result$1; }, []); deferRun.push(() => { const newIndex = getCalculateOffset(updateOffsetMap, start.offset); const newSource = newCode.slice(start.offset + newIndex, end.offset + newIndex); allChanges.push({ before, after, name: originClassName, source: newSource, tag, attr, prefix, media, start, end }); }); } return all; }); deferRun.forEach((run) => run()); if (debug) console.log("[DEBUG] transformCss finished, resolving conflicts:", JSON.stringify({ allChangesCount: allChanges.length }, null, 2)); return await resolveConflictClass(allChanges, newCode, isJsx, updateOffsetMap, debug); } async function importCss(code, style, filepath, isJsx, debug = false, globalCss) { if (debug) console.log("[DEBUG] importCss started:", JSON.stringify({ filepath, styleLength: style.length, hasImports: /@import/.test(style) }, null, 2)); const originCode = code; for await (const match of style.matchAll(/@import (url\()?["']*([\w./\-]*)["']*\)?;/g)) { if (!match) continue; if (debug) console.log("[DEBUG] Processing CSS import:", JSON.stringify({ importUrl: match[2] }, null, 2)); const url = node_path.default.resolve(filepath, "..", match[2]); const content = await node_fs_promises.default.readFile(node_path.default.resolve(filepath, "..", url), "utf-8"); const type = getCssType(url); const css = await compilerCss(content, type, url, globalCss); const [_, beforeStyle] = code.match(/<style.*>(.*)<\/style>/s); code = code.replace(beforeStyle, ""); const vue = wrapperVueTemplate(code, css); const transfer = await transformVue(vue, { isJsx, isRem, filepath, globalCss, debug }); if (diffTemplateStyle(transfer, vue)) { code = originCode; continue; } if (isEmptyStyle(transfer)) { code = wrapperVueTemplate(transfer, beforeStyle.replace(match[0], "")); continue; } const restStyle = getStyleScoped(transfer); node_fs_promises.default.writeFile(url.replace(`.${type}`, `${TRANSFER_FLAG}.${type}`), restStyle, "utf-8"); code = wrapperVueTemplate(transfer.replace(/<style scoped>.*<\/style>/s, ""), beforeStyle); continue; } return code; } async function resolveConflictClass(allChange, code, isJsx = true, updateOffset, debug = false) { if (debug) console.log("[DEBUG] resolveConflictClass started:", JSON.stringify({ allChangesCount: allChange.length, isJsx }, null, 2)); const changes = findSameSource(allChange); let result = code; if (debug) console.log("[DEBUG] Found conflict groups:", JSON.stringify({ groupsCount: Object.keys(changes).length, groups: Object.keys(changes).map((key) => ({ key, changesCount: changes[key].length })) }, null, 2)); for await (const key of Object.keys(changes)) { const value = changes[key]; const { tag, prefix, media, source, start: { offset }, end: { offset: offsetEnd } } = value[0]; let [after, transform] = await getConflictClass(value, debug); if (!after) { if (debug) console.log("[DEBUG] No conflict resolution needed for group:", key); continue; } if (debug) console.log("[DEBUG] Conflict resolved for group:", JSON.stringify({ key, after, originalSource: source }, null, 2)); const newResult = transform(result); result = newResult; const target = transform(source); if (media) after = `${media}:${after}`; if (prefix) if ((0, transform_to_unocss_core.isNot)(prefix)) { const match = target.match(/<[^>]*(class="[^"]+)[^>]*/); if (match) after = after.replace(/class="(\[&:not\([\w\s\-.#]+\)\]:[\w\-.]+)"\s*/, (_, v) => { const updateText = ` ${v}`; result = result.replace(match[1], `${match[1]}${updateText}`); return ""; }); } else after = after.replace(/="\[/g, "-\"["); const returnValue = isJsx || after.replace(/(?:[\w\-]+|\[[^\]]+\])=("{1})(.*?)\1/g, "").includes("[") ? after.replace(/\[([^\]]+)\]/g, (all, v) => all.replace(v, (0, transform_to_unocss_core.joinWithUnderLine)(v))).replace(/-(rgba?([^)]+))/g, "-[$1]").replace(/((?:[\w\-]+|\[[^\]]+\])(?::[\w\-]+|-\[[^\]]*\])*)=(['"]{1})(.*?)\2/g, (_all, prefix$1, _, content) => { const splitContent = content.split(/(?<!\[[^\]]*)\s+/).filter(Boolean); return splitContent.map((item) => `${prefix$1}-${item}`).join(` `); }) : after; const getUpdateOffset = getCalculateOffset(updateOffset, offset); const startOffset = offset + getUpdateOffset; const endOffset = offsetEnd + getUpdateOffset; const start = result.slice(startOffset, endOffset); if (isJsx || after.replace(/[\w\-]+=("{1})(.*?)\1/g, "").includes("[")) { const newReg = new RegExp(`^<${tag}(?:[^\/'">]|"[^"]*"|'[^']*')*[^:]class=["']([^"']+)["']([^\/'">]|"[^"]*"|'[^']*')*\/?>`, "s"); const matcher = target.match(newReg); if (matcher) { const insertText$1 = ` ${returnValue}`; result = result.slice(0, startOffset) + start.replace(`class="${matcher[1]}"`, `class="${matcher[1]}${insertText$1}"`) + result.slice(endOffset); updateOffset[offset] = insertText$1.length; continue; } const insertText = ` class="${returnValue}"`; result = result.slice(0, startOffset) + start.replace(`<${tag}`, `<${tag}${insertText}`) + result.slice(endOffset); updateOffset[offset] = insertText.length; continue; } result = result.slice(0, startOffset) + start.replace(`<${tag}`, `<${tag} ${returnValue}`) + result.slice(endOffset); } return result; } function calculateWeight(c) { const data = c.split(" ").filter((i) => i !== "+" && i !== ">"); let num = 0; data.forEach((item) => { item.replace(/#\w+/g, () => { num += 100; return ""; }); item.replace(/.\w+/, () => { num += 10; return ""; }); item.replace(/^\w+/, () => { num += 10; return ""; }); item.replace(/\[[\w\s='"-]+\]/g, () => { num += 10; return ""; }); item.replace(/:\w+/g, () => { num += 1; return ""; }); }); return num; } function findSameSource(allChange) { const result = {}; allChange.forEach((item) => { const { source, start, end } = item; const key = `${source}:${start.offset}:${end.offset}`; if (!result[key]) result[key] = []; result[key].push(item); }); return result; } const skipTransformFlag = Symbol("skipTransformFlag"); async function getConflictClass(allChange, debug = false) { if (debug) console.log("[DEBUG] getConflictClass started:", JSON.stringify({ changesCount: allChange.length, changes: allChange.map((c) => ({ name: c.name, before: c.before, after: c.after })) }, null, 2)); let map = {}; let transform = (code) => code; for await (const item of allChange) { const { before, name, source, attr, after, prefix, media } = item; const pre = prefix ? `${prefix}|` : ""; const beforeArr = before.split(";").filter(Boolean); const data = beforeArr.map((item$1) => { const [key, value] = item$1.trim().split(":"); return [`${pre}${key}`, value]; }); data.forEach((item$1) => { const [key, value] = item$1; if (value === void 0) return; if (!map[key]) map[key] = [calculateWeight(name), value]; else { const [preWeight] = map[key]; if (preWeight === skipTransformFlag) return; const curWeight = calculateWeight(name); if (+curWeight >= +preWeight) map[key] = [+curWeight, value]; } }); if (attr) { const res = await transformUnocssBack(attr.map((i) => { if (prefix) return `${prefix}="${i}"`; if (media) return `${media}:${i}`; return i; })); Object.keys(map).forEach((i) => { const index = res.findIndex((r) => r === i); if (index !== -1) { const inline = item.attr[index]; if ((inline === null || inline === void 0 ? void 0 : inline.endsWith("!")) || !(after === null || after === void 0 ? void 0 : after.endsWith("!"))) return delete map[i]; else transform = (code) => code.replace(source, source.replace(` ${inline}`, "")); } }); } } const joinMap = Object.keys(map).map((key) => { const value = map[key][1]; return `${key}:${value}`; }).join(";"); const { transformedResult, newStyle } = (0, transform_to_unocss_core.transformStyleToUnocssPre)(joinMap); if (transformedResult) { map = newStyle.split(";").reduce( (acc, item) => { const [key, value] = item.trim().split(":"); if (value !== void 0) acc[key] = [map[key][0], value]; return acc; }, // 将 transformedResult 赋值给 map // map[] {} ); map[transformedResult] = [1, skipTransformFlag]; } return [Object.keys(map).reduce((result, key) => { const keys = key.split("|"); const styleKey = keys[keys.length - 1]; let prefix = keys.length > 1 ? keys[0] : ""; let transferCss = map[key][1] === skipTransformFlag ? key : (0, transform_to_unocss_core.transformStyleToUnocss)(`${styleKey}:${map[key][1]}`, isRem)[0]; if (debug) console.log("[DEBUG] Processing map key:", JSON.stringify({ key, styleKey, prefix, transferCss, mapValue: map[key] }, null, 2)); const match = transferCss.match(/(\S*)="\[([^\]]*)\]"/); if (match) { var _match$input; transferCss = `${(_match$input = match.input) === null || _match$input === void 0 ? void 0 : _match$input.replace(match[0], match[0].replace(/="\[([^\]]*)\]"/, (_, v) => `-[${v}]`).replace(/="([^"]*)"/, "-$1"))}`; } const _transferCss = prefix ? (0, transform_to_unocss_core.isNot)(prefix) ? `class="${prefix}${transferCss.replace(/="\[([^\]]*)\]"/g, (_, v) => `-[${v}]`).replace(/="([^"]*)"/, "-$1")}"` : `${prefix}="${transferCss.replace(/="\[([^\]]*)\]"/g, (_, v) => `-[${v}]`).replace(/="([^"]*)"/, "-$1")}"` : transferCss; if (!prefix) { const reg = /^(\S*)="[^"]*"$/; if (reg.test(transferCss)) prefix = transferCss.match(reg)[1]; } if (prefix) { const prefixReg1 = new RegExp(`(?<!\\S)${(0, __unocss_core.escapeRegExp)(prefix)}(?!\\S)`); if (prefixReg1.test(result)) return result.replace(prefixReg1, (all) => all.replace(prefix, _transferCss)); const prefixReg2 = new RegExp(`(?<!\\S)${(0, __unocss_core.escapeRegExp)(prefix)}=`); if (prefixReg2.test(result)) { if ((0, transform_to_unocss_core.isNot)(prefix)) { const newPrefix = prefix.replace(/[[\]()]/g, (all) => `\\${all}`); const reg$1 = new RegExp(`${(0, __unocss_core.escapeRegExp)(newPrefix)}([\\w\\:\\-;\\[\\]\\/\\+%]+)`); return result.replace(reg$1, (all) => `${all}:${transferCss}`); } const reg = new RegExp(`${(0, __unocss_core.escapeRegExp)(prefix)}=(["]{1})(.*?)\\1`); return result.replace(reg, (all, _, v) => { const unique = [...new Set(v.split(" ").concat(_transferCss.slice(prefix.length + 2, -1).split(" ")))].join(" "); if (v) return all.replace(v, unique); return `${prefix}="${unique.trim()}"`; }); } } return `${result}${_transferCss} `; }, "").trim(), transform]; } function getCalculateOffset(offsetMap, offset) { return Object.keys(offsetMap).reduce((result, key) => { if (+key <= offset) result += offsetMap[key]; return result; }, 0); } //#endregion //#region src/transformInlineStyle.ts const styleReg$1 = /<([\w\-]+)[^/>]*[^:]style="([^"]+)"[^>]*>/g; const removeStyleReg = / style=("{1})(.*?)\1/; const templateReg = /^<template>(.*)<\/template>$/ms; const commentReg = /<!--.*-->/gs; function transformInlineStyle(code, isJsx, isRem$1, debug = false) { const match = code.match(templateReg); if (!match) return code; let templateMatch = match[1]; const commentMap = {}; let count = 0; const commentPrefix = "__commentMap__"; templateMatch = templateMatch.replace(commentReg, (comment) => { count++; commentMap[count] = comment; return `${commentPrefix}${count}`; }); templateMatch.replace(styleReg$1, (target, tag, inlineStyle) => { const [after, noMap] = isJsx ? (0, transform_to_unocss_core.toUnocssClass)(inlineStyle, isRem$1) : (0, transform_to_unocss_core.transformStyleToUnocss)(inlineStyle, isRem$1, debug); if (debug) console.log("[DEBUG] transformInlineStyle processing:", JSON.stringify({ tag, inlineStyle, after, noMapLength: (noMap === null || noMap === void 0 ? void 0 : noMap.length) || 0 }, null, 2)); if (isJsx) { const newReg = new RegExp(`<${tag}.*\\sclass=(["']{1})(.*?)\\1`, "s"); const matcher = target.match(newReg); if (matcher) return templateMatch = templateMatch.replace(target, target.replace(removeStyleReg, "").replace(`class="${matcher[2]}"`, noMap.length ? `class="${matcher[2]} ${after}" style="${noMap.map((item) => item && item.trim()).join(";")}"` : `class="${matcher[2]} ${after}"`)); return templateMatch = templateMatch.replace(target, target.replace(removeStyleReg, "").replace(`<${tag}`, noMap.length ? `<${tag} class="${after}" style="${noMap.map((item) => item && item.trim()).join(";")}` : `<${tag} class="${after}"`)); } return templateMatch = templateMatch.replace(target, target.replace(removeStyleReg, "").replace(`<${tag}`, noMap.length ? `<${tag} ${after} style="${noMap.map((item) => item && item.trim()).join(";")}"` : `<${tag} ${after}`)); }); Object.keys(commentMap).forEach((key) => { const commentKey = `${commentPrefix}${key}`; const value = commentMap[key]; templateMatch = templateMatch.replace(commentKey, value); }); return code.replace(templateReg, `<template>${templateMatch}</template>`); } //#endregion //#region src/transformMedia.ts const mediaReg = /@media([\s\w]*)\(([\w-]+):\s*(\w+)\)\s*\{([\s\w.{}\-:;]*)\}/g; const mediaSingleReg = /@media([\s\w]*)\(([\w-]+):\s*(\w+)\)\s*\{([\s\w.{}\-:;]*)\}/; const emptyMediaReg = /@media([\s\w]*)\(([\w-]+):\s*(\w+)\)\s*\{\s*\}/g; const valMap = { "640px": "sm", "768px": "md", "1024px": "lg", "1280px": "xl", "1536px": "2xl" }; /** * Transforms CSS @media queries to UnoCSS responsive utilities * @param code - The code containing @media queries * @param isJsx - Whether the code is JSX/TSX format * @param isRem - Whether to convert px values to rem * @param filepath - The file path for resolving CSS imports within media queries * @param debug - Whether to enable debug logging * @param globalCss - Global CSS configuration for preprocessors * @returns A tuple of [transformed code, restore function] */ async function transformMedia(code, isJsx, isRem$1, filepath, debug = false, globalCss) { const transferBackMap = []; let result = code; const matcher = code.match(mediaReg); if (!matcher) { if (debug) console.log("[DEBUG] transformMedia: No @media queries found"); return returnValue(result); } if (debug) console.log("[DEBUG] transformMedia started:", JSON.stringify({ filepath, isJsx, isRem: isRem$1, mediaQueriesCount: matcher.length }, null, 2)); for await (const item of matcher) { const [all, pre, key, val, inner] = item.match(mediaSingleReg); const tempFlag = `/* __transformMedia${Math.random()}__ */`; const value = valMap[val]; if (debug) console.log("[DEBUG] transformMedia processing query:", JSON.stringify({ all: `${all.substring(0, 100)}...`, key, val, mappedValue: value, hasPrefix: !!pre.trim() }, null, 2)); if (!value) { result = result.replace(all, tempFlag); transferBackMap.push((r) => r.replace(tempFlag, all)); continue; } if (pre.trim()) { const transfer$1 = await transformCss(inner, result, `max-${value}`, isJsx, filepath, isRem$1, debug, globalCss); if (transfer$1 !== result) { result = transfer$1.replace(emptyMediaReg, ""); transferBackMap.push((r) => r.replace(tempFlag, transfer$1)); continue; } result = result.replace(all, tempFlag); transferBackMap.push((r) => r.replace(tempFlag, all)); continue; } let mapValue = value; if (key === "prefers-reduced-motion") mapValue = `${(0, transform_to_unocss_core.getLastName)(key)}-${val === "no-preference" ? "safe" : val}`; const transfer = (await transformCss(inner, result, mapValue, isJsx, filepath, isRem$1, debug, globalCss)).replace(emptyMediaReg, ""); result = transfer.replace(all, tempFlag); transferBackMap.push((r) => r.replace(tempFlag, all)); } return returnValue(result); function returnValue(result$1) { return [result$1, (r) => transferBackMap.reduce((result$2, fn) => fn(result$2), r)]; } } //#endregion //#region src/transformVue.ts async function transformVue(code, options) { const { isJsx, filepath, isRem: isRem$1, globalCss, debug } = options || {}; const { parse: parse$1 } = await getVueCompilerSfc(); if (debug) console.log("[DEBUG] transformVue started:", JSON.stringify({ filepath, isJsx, isRem: isRem$1, codeLength: code.length }, null, 2)); const { descriptor: { template, styles }, errors } = parse$1(code); if (errors.length || !template) { if (debug && errors.length) console.log("[DEBUG] transformVue parse errors:", errors); return code; } code = transformInlineStyle(code, isJsx, isRem$1, debug); if (debug) console.log("[DEBUG] After inline style transformation"); const [transferMediaCode, transformBack] = await transformMedia(code, isJsx, isRem$1, filepath, debug, globalCss); code = transferMediaCode; if (styles.length) { if (debug) console.log("[DEBUG] Processing styles:", JSON.stringify({ stylesCount: styles.length, firstStyle: styles[0] }, null, 2)); const { attrs: { scoped }, content: style, lang = "css" } = styles[0]; const css = await compilerCss(style, lang, filepath, globalCss); if (css) { if (debug) console.log("[DEBUG] CSS compiled successfully:", JSON.stringify({ originalStyleLength: style.length, compiledCssLength: css.length, scoped: !!scoped }, null, 2)); code = code.replace(style, `\n${css}\n`).replace(` lang="${lang}"`, ""); if (scoped) code = await transformCss(css, code, "", isJsx, filepath, isRem$1, debug, globalCss); } } code = transformBack(code); if (debug) console.log("[DEBUG] transformVue completed:", JSON.stringify({ finalCodeLength: code.length }, null, 2)); return prettierCode(code); } //#endregion //#region src/transformAstro.ts async function transformAstro(code, options) { const { filepath, isRem: isRem$1, globalCss, debug = false } = options || {}; const match = code.match(/(---.*---)?(.*(?=<style>))(<style>.*<\/style>)?/s); if (!match) return code; const [_all, _js, template, css] = match; const _css = css ? css.replace(/<style>(.*)<\/style>/s, "$1") : ""; const _template = wrapperVueTemplate(template, _css); const vue = await transformVue(_template, { isJsx: true, isRem: isRem$1, globalCss, filepath, debug }); vue.replace(/<template>(.*)<\/template>\s*<style scoped>(.*)<\/style>/s, (_, newTemplate, newCss) => code = code.replace(template, newTemplate).replace(css, newCss)); return prettierCode(code); } //#endregion //#region src/transformHtml.ts const linkCssReg = /<link.*href="(.+css)".*>/g; const styleReg = /\s*<style[^>]*>(.*)<\/style>\s*/s; async function transformHtml(code, options) { const { filepath, isRem: isRem$1, globalCss, debug = false } = options || {}; const css = await getLinkCss(code, filepath); const style = getStyleCss(code); const newCode = await generateNewCode(css, style, code, isRem$1, globalCss, debug); return prettierCode(newCode); } async function getLinkCss(code, filepath) { const css = []; for (const match of code.matchAll(linkCssReg)) try { const url = match[0]; const cssUrl = node_path.default.resolve(filepath, "../", match[1]); css.push({ url, content: await node_fs_promises.default.readFile(cssUrl, "utf-8") }); } catch (error) { throw new Error(error.toString()); } return css; } function getStyleCss(code) { const match = code.match(styleReg); if (!match) return ""; return match[1]; } function getBody(code) { const match = code.match(/<body[^>]*>.*<\/body>/s); if (!match) return ""; return match[0]; } async function generateNewCode(css, style, code, isRem$1, globalCss, debug = false) { let template = getBody(code); const originBody = template; if (style) { const vue = wrapperVueTemplate(template, style); const transferCode = await transformVue(vue, { isJsx: true, isRem: isRem$1, globalCss, debug }); template = transferCode; if (transferCode.includes("<style scoped></style>")) code = code.replace(styleReg, ""); } if (css.length) for (const c of css) { const { url, content } = c; const vue = wrapperVueTemplate(template, content); const transferCode = await transformVue(vue, { isJsx: true, isRem: isRem$1, globalCss, debug }); if (diffTemplateStyle(template, transferCode)) code = code.replace(url, ""); template = transferCode; } return code.replace(originBody, getBody(template)); } //#endregion //#region src/transformJsx.ts async function transformJsx(code, options) { const { filepath, isRem: isRem$1, globalCss, debug = false } = options || {}; try { const ast = (0, __babel_core.parse)(code, { babelrc: false, comments: true, plugins: [[__vue_babel_plugin_jsx.default]] }); let container = null; let css = ""; let cssPath = ""; (0, __babel_core.traverse)(ast, { enter({ node }) { if (node.type === "JSXElement") { if (container) return; container = node; } if (node.type === "ImportDeclaration") { const value = node.source.value; if (value.endsWith(".css")) css += node_fs.default.readFileSync(cssPath = node_path.default.resolve(filepath, "../", value), "utf-8"); } } }); const jsxCode = code.slice(container.start, container.end); const isJsx = jsxCode.includes("className"); const wrapperVue = `<template>${jsxCode}</template> <style scoped> ${css} </style>`; const vueTransfer = await transformVue(wrapperVue, { isJsx, isRem: isRem$1, globalCss, filepath, debug }); if (cssPath) { const cssTransfer = vueTransfer.match(/<style scoped>(.*)<\/style>/s)[1]; node_fs.default.promises.writeFile(cssPath.replace(".css", ".__unocss_transfer__.css"), cssTransfer, "utf-8"); } const jsxTransfer = vueTransfer.match(/<template>(.*)<\/template>/s)[1]; return code.replace(jsxCode, jsxTransfer); } catch (error) { return code; } } //#endregion //#region src/transformSvelte.ts async function transformSvelte(code, options) { const { filepath, isRem: isRem$1, globalCss, debug = false } = options || {}; const match = code.match(/(<script.*<\/script>)?(.*(?=<style>))(<style>.*<\/style>)?/s); if (!match) return code; const [_all, _js, template, css] = match; const _css = css ? css.replace(/<style>(.*)<\/style>/s, "$1") : ""; const _template = wrapperVueTemplate(template, _css); const vue = await transformVue(_template, { isJsx: true, isRem: isRem$1, globalCss, filepath, debug }); vue.replace(/<template>(.*)<\/template>\s*<style scoped>(.*)<\/style>/s, (_, newTemplate, newCss) => code = code.replace(template, newTemplate).replace(css, newCss)); return prettierCode(code); } //#endregion //#region src/transformCode.ts async function transformCode(code, options) { const { filepath, isRem: isRem$1, type, isJsx = true, globalCss, debug } = options || {}; if (debug) console.log("[DEBUG] transformCode started:", JSON.stringify({ filepath, type, isJsx, isRem: isRem$1, codeLength: code.length })); if (type === "tsx") return transformJsx(code, { filepath, isRem: isRem$1, globalCss, debug }); if (type === "html") return transformHtml(code, { filepath, globalCss, debug }); if (type === "svelte") return transformSvelte(code, { filepath, isRem: isRem$1, globalCss, debug }); if (type === "astro") return transformAstro(code, { filepath, isRem: isRem$1, globalCss, debug }); return transformVue(code, { isJsx, filepath, isRem: isRem$1, globalCss, debug }); } //#endregion Object.defineProperty(exports, 'TRANSFER_FLAG', { enumerable: true, get: function () { return TRANSFER_FLAG; } }); Object.defineProperty(exports, '__toESM', { enumerable: true, get: function () { return __toESM; } }); Object.defineProperty(exports, 'transformAstro', { enumerable: true, get: function () { return transformAstro; } }); Object.defineProperty(exports, 'transformCode', { enumerable: true, get: function () { return transformCode; } }); Object.defineProperty(exports, 'transformHtml', { enumerable: true, get: function () { return transformHtml; } }); Object.defineProperty(exports, 'transformJsx', { enumerable: true, get: function () { return transformJsx; } }); Object.defineProperty(exports, 'transformSvelte', { enumerable: true, get: function () { return transformSvelte; } }); Object.defineProperty(exports, 'transformVue', { enumerable: true, get: function () { return transformVue; } });