UNPKG

@maizzle/framework

Version:

Maizzle is a framework that helps you quickly build HTML emails with Tailwind CSS.

119 lines (118 loc) 4.44 kB
import { walk } from "../utils/ast/walker.js"; import "../utils/ast/index.js"; import { compileTailwindCss } from "../utils/compileTailwindCss.js"; import { decodeStyleEntities } from "../utils/decodeStyleEntities.js"; import { dirname, relative, resolve } from "pathe"; //#region src/transformers/tailwindcss.ts /** * Check if CSS content uses Tailwind features that require source scanning. * * Only CSS that imports Tailwind (or @maizzle/tailwindcss) needs @source * directives. Plain CSS without Tailwind imports doesn't need scanning * and would pass through @source directives unconsumed. */ function usesTailwind(css) { return /((@import|@reference)\s+["'](tailwindcss|@maizzle\/tailwindcss)|@tailwind\s)/.test(css); } /** * Build @source directives for Tailwind CSS scanning. * * Configures two types of sources: * 1. Exclusions for output dir and user-configured paths * 2. Inline source with all class attribute values from the rendered DOM, * capturing classes from all components (built-in + user), dynamic * expressions, and the template itself — Tailwind's scanner handles * the actual class extraction from these raw values */ function buildSourceDirectives(dom, config, fromDir) { const directives = []; const excludePaths = [resolve(config.output?.path ?? "dist"), ...(config.css?.exclude ?? []).map((p) => resolve(p))]; for (const p of excludePaths) directives.push(`@source not "${relative(fromDir, resolve(p))}";`); /** * Inline source: collect all class attribute values from the rendered DOM. * After Vue SSR, the DOM contains every class from every component * (built-in framework components, user components, dynamic * bindings). We pass these raw values to Tailwind's * scanner via @source inline(). */ const classes = []; walk(dom, (n) => { const cls = n.attribs?.class; if (cls) classes.push(cls); }); if (classes.length) directives.push(`@source inline("${classes.join(" ")}");`); return directives.join("\n"); } /** * Tailwind CSS transformer. * * Compiles CSS inside <style> tags in the DOM using * @tailwindcss/postcss, then lowers modern CSS syntax with lightningcss. * * Configures Tailwind sources to scan: * - Rendered class attributes (via `@source inline`) for all classes from all components * - User project files (via Tailwind's auto-detection from base/from path) * * User `@source` and `@source not directives` in style tags are preserved. * Source directives are only added to style tags that import Tailwind. * * Runs as the first transformer in the pipeline so that subsequent * transformers (inliner, purge, etc.) work with fully compiled CSS. */ async function tailwindcss(dom, config, filePath) { const styleTags = []; walk(dom, (node) => { if (node.name !== "style") return; const el = node; /** * `raw` opts out of compilation entirely (marker is consumed here). * `embed`/`data-embed` only signal "preserve tag after inlining" * — they still need to go through compile so Tailwind/@apply * resolves. */ if ("raw" in el.attribs) { delete el.attribs.raw; return; } const rawContent = el.children.filter((child) => child.type === "text").map((child) => child.data).join(""); if (!rawContent.trim()) return; styleTags.push({ node: el, cssContent: decodeStyleEntities(rawContent) }); }); if (!styleTags.length) return dom; const fromPath = filePath ?? resolve(process.cwd(), "template.vue"); const fromDir = dirname(fromPath); const sourceDirectives = styleTags.some(({ cssContent }) => usesTailwind(cssContent)) ? buildSourceDirectives(dom, config, fromDir) : ""; for (let i = 0; i < styleTags.length; i++) { const { node, cssContent } = styleTags[i]; /** * Only add source directives to style tags that import Tailwind — * plain CSS doesn't need them and @tailwindcss/postcss would * leave the directives unconsumed in the output. */ const fullCss = usesTailwind(cssContent) ? `${cssContent}\n${sourceDirectives}` : cssContent; try { node.children = [{ type: "text", data: await compileTailwindCss(fullCss, config, `${fromPath}?style=${i}`), parent: node }]; } catch { /** * If CSS processing fails, still replace with decoded content * so HTML entities don't break the CSS. */ node.children = [{ type: "text", data: cssContent, parent: node }]; } } return dom; } //#endregion export { tailwindcss }; //# sourceMappingURL=tailwindcss.js.map