@maizzle/framework 6.1.0 → 6.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/build.js +2 -1
  2. package/dist/build.js.map +1 -1
  3. package/dist/config/index.js.map +1 -1
  4. package/dist/plugins/postcss/mergeMediaQueries.js +2 -1
  5. package/dist/plugins/postcss/mergeMediaQueries.js.map +1 -1
  6. package/dist/plugins/postcss/resolveMaizzleImports.js.map +1 -1
  7. package/dist/render/buildTemplate.js +4 -2
  8. package/dist/render/buildTemplate.js.map +1 -1
  9. package/dist/render/createRenderer.d.ts +7 -0
  10. package/dist/render/createRenderer.d.ts.map +1 -1
  11. package/dist/render/createRenderer.js +46 -3
  12. package/dist/render/createRenderer.js.map +1 -1
  13. package/dist/serve.js +19 -10
  14. package/dist/serve.js.map +1 -1
  15. package/dist/server/compatibility.js +2 -1
  16. package/dist/server/compatibility.js.map +1 -1
  17. package/dist/server/linter.js +2 -1
  18. package/dist/server/linter.js.map +1 -1
  19. package/dist/server/sfc-utils.js.map +1 -1
  20. package/dist/server/ui/.vite/deps/@lucide_vue.js +6845 -6285
  21. package/dist/server/ui/.vite/deps/@lucide_vue.js.map +1 -1
  22. package/dist/server/ui/.vite/deps/@vueuse_core.js +2 -6
  23. package/dist/server/ui/.vite/deps/@vueuse_core.js.map +1 -1
  24. package/dist/server/ui/.vite/deps/@vueuse_shared.js +1 -1
  25. package/dist/server/ui/.vite/deps/@vueuse_shared.js.map +1 -1
  26. package/dist/server/ui/.vite/deps/_metadata.json +15 -15
  27. package/dist/server/ui/.vite/deps/culori.js +8 -6
  28. package/dist/server/ui/.vite/deps/culori.js.map +1 -1
  29. package/dist/server/ui/.vite/deps/reka-ui.js +63 -73
  30. package/dist/server/ui/.vite/deps/reka-ui.js.map +1 -1
  31. package/dist/server/ui/.vite/deps/tailwind-merge.js.map +1 -1
  32. package/dist/server/ui/.vite/deps/vue-router.js +5 -13
  33. package/dist/server/ui/.vite/deps/vue-router.js.map +1 -1
  34. package/dist/server/ui/.vite/deps/vue.js +1 -1
  35. package/dist/server/ui/.vite/deps/{vue.runtime.esm-bundler-BHD3r-bP.js → vue.runtime.esm-bundler-S54atuNJ.js} +323 -259
  36. package/dist/server/ui/.vite/deps/{vue.runtime.esm-bundler-BHD3r-bP.js.map → vue.runtime.esm-bundler-S54atuNJ.js.map} +1 -1
  37. package/dist/transformers/format.js +2 -1
  38. package/dist/transformers/format.js.map +1 -1
  39. package/dist/transformers/index.d.ts +1 -1
  40. package/dist/transformers/index.d.ts.map +1 -1
  41. package/dist/transformers/index.js +4 -4
  42. package/dist/transformers/index.js.map +1 -1
  43. package/dist/transformers/inlineCss.js +3 -2
  44. package/dist/transformers/inlineCss.js.map +1 -1
  45. package/dist/transformers/inlineLink.js +2 -1
  46. package/dist/transformers/inlineLink.js.map +1 -1
  47. package/dist/transformers/minify.js +2 -1
  48. package/dist/transformers/minify.js.map +1 -1
  49. package/dist/transformers/tailwindComponent.d.ts +1 -1
  50. package/dist/transformers/tailwindComponent.d.ts.map +1 -1
  51. package/dist/transformers/tailwindComponent.js +26 -7
  52. package/dist/transformers/tailwindComponent.js.map +1 -1
  53. package/dist/transformers/tailwindcss.d.ts +10 -3
  54. package/dist/transformers/tailwindcss.d.ts.map +1 -1
  55. package/dist/transformers/tailwindcss.js +30 -14
  56. package/dist/transformers/tailwindcss.js.map +1 -1
  57. package/dist/transformers/urlQuery.js +2 -1
  58. package/dist/transformers/urlQuery.js.map +1 -1
  59. package/dist/types/config.d.ts +11 -0
  60. package/dist/types/config.d.ts.map +1 -1
  61. package/dist/utils/cssBox.js +2 -6
  62. package/dist/utils/cssBox.js.map +1 -1
  63. package/package.json +2 -2
@@ -22,7 +22,8 @@ const DEFAULT_OPTIONS = {
22
22
  * const pretty = await format(html, { useTabs: true, tabWidth: 4 })
23
23
  */
24
24
  async function format(html, options = {}) {
25
- return (await format$1("input.html", html, defu$1(options, DEFAULT_OPTIONS))).code;
25
+ const merged = defu$1(options, DEFAULT_OPTIONS);
26
+ return (await format$1("input.html", html, merged)).code;
26
27
  }
27
28
  //#endregion
28
29
  export { format };
@@ -1 +1 @@
1
- {"version":3,"file":"format.js","names":["oxfmt","merge"],"sources":["../../src/transformers/format.ts"],"sourcesContent":["import { format as oxfmt } from 'oxfmt'\nimport { defu as merge } from 'defu'\nimport type { FormatConfig } from 'oxfmt'\n\nexport type { FormatConfig } from 'oxfmt'\n\nconst DEFAULT_OPTIONS: FormatConfig = {\n printWidth: 320,\n htmlWhitespaceSensitivity: 'ignore',\n embeddedLanguageFormatting: 'off',\n}\n\n/**\n * Pretty-print an HTML string with `oxfmt`. Maizzle defaults\n * (`printWidth: 320`, `htmlWhitespaceSensitivity: 'ignore'`,\n * `embeddedLanguageFormatting: 'off'`) are merged underneath any options\n * you pass.\n *\n * @param html HTML string to format.\n * @param options [oxfmt `FormatConfig`](https://github.com/oxc-project/oxfmt).\n * @returns The formatted HTML string.\n *\n * @example\n * import { format } from '@maizzle/framework'\n *\n * const pretty = await format(html, { useTabs: true, tabWidth: 4 })\n */\nexport async function format(html: string, options: FormatConfig = {}): Promise<string> {\n const merged = merge(options, DEFAULT_OPTIONS)\n const result = await oxfmt('input.html', html, merged)\n return result.code\n}\n"],"mappings":";;;AAMA,MAAM,kBAAgC;CACpC,YAAY;CACZ,2BAA2B;CAC3B,4BAA4B;AAC9B;;;;;;;;;;;;;;;;AAiBA,eAAsB,OAAO,MAAc,UAAwB,CAAC,GAAoB;CAGtF,QAAO,MADcA,SAAM,cAAc,MAD1BC,OAAM,SAAS,eACsB,CAAC,EAAA,CACvC;AAChB"}
1
+ {"version":3,"file":"format.js","names":["merge","oxfmt"],"sources":["../../src/transformers/format.ts"],"sourcesContent":["import { format as oxfmt } from 'oxfmt'\nimport { defu as merge } from 'defu'\nimport type { FormatConfig } from 'oxfmt'\n\nexport type { FormatConfig } from 'oxfmt'\n\nconst DEFAULT_OPTIONS: FormatConfig = {\n printWidth: 320,\n htmlWhitespaceSensitivity: 'ignore',\n embeddedLanguageFormatting: 'off',\n}\n\n/**\n * Pretty-print an HTML string with `oxfmt`. Maizzle defaults\n * (`printWidth: 320`, `htmlWhitespaceSensitivity: 'ignore'`,\n * `embeddedLanguageFormatting: 'off'`) are merged underneath any options\n * you pass.\n *\n * @param html HTML string to format.\n * @param options [oxfmt `FormatConfig`](https://github.com/oxc-project/oxfmt).\n * @returns The formatted HTML string.\n *\n * @example\n * import { format } from '@maizzle/framework'\n *\n * const pretty = await format(html, { useTabs: true, tabWidth: 4 })\n */\nexport async function format(html: string, options: FormatConfig = {}): Promise<string> {\n const merged = merge(options, DEFAULT_OPTIONS)\n const result = await oxfmt('input.html', html, merged)\n return result.code\n}\n"],"mappings":";;;AAMA,MAAM,kBAAgC;CACpC,YAAY;CACZ,2BAA2B;CAC3B,4BAA4B;AAC9B;;;;;;;;;;;;;;;;AAiBA,eAAsB,OAAO,MAAc,UAAwB,CAAC,GAAoB;CACtF,MAAM,SAASA,OAAM,SAAS,eAAe;CAE7C,QAAO,MADcC,SAAM,cAAc,MAAM,MAAM,EAAA,CACvC;AAChB"}
@@ -32,7 +32,7 @@ import { TailwindBlock } from "../composables/renderContext.js";
32
32
  * 15. Prettify
33
33
  * 16. Minify
34
34
  */
35
- declare function runTransformers(html: string, config: MaizzleConfig, filePath?: string, doctype?: string, tailwindBlocks?: TailwindBlock[]): Promise<string>;
35
+ declare function runTransformers(html: string, config: MaizzleConfig, filePath?: string, doctype?: string, tailwindBlocks?: TailwindBlock[], sourceFiles?: string[]): Promise<string>;
36
36
  //#endregion
37
37
  export { runTransformers };
38
38
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/transformers/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyDsB,gBACpB,cACA,QAAQ,eACR,mBACA,kBACA,iBAAiB,kBAChB"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/transformers/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyDsB,gBACpB,cACA,QAAQ,eACR,mBACA,kBACA,iBAAiB,iBACjB,yBACC"}
@@ -2,8 +2,8 @@ import { parse } from "../utils/ast/parser.js";
2
2
  import { serialize } from "../utils/ast/serializer.js";
3
3
  import "../utils/ast/index.js";
4
4
  import { inlineLinkDom } from "./inlineLink.js";
5
- import { tailwindComponent } from "./tailwindComponent.js";
6
5
  import { tailwindcss } from "./tailwindcss.js";
6
+ import { tailwindComponent } from "./tailwindComponent.js";
7
7
  import { safeSelectorsDom } from "./safeSelectors.js";
8
8
  import { attributeToStyleDom } from "./attributeToStyle.js";
9
9
  import { inlineCssDom } from "./inlineCss.js";
@@ -55,7 +55,7 @@ import { minify } from "./minify.js";
55
55
  * 15. Prettify
56
56
  * 16. Minify
57
57
  */
58
- async function runTransformers(html, config, filePath, doctype, tailwindBlocks) {
58
+ async function runTransformers(html, config, filePath, doctype, tailwindBlocks, sourceFiles) {
59
59
  /**
60
60
  * Per-transformer skip map — only honored when useTransformers is an object.
61
61
  * Whole-pipeline opt-out (`useTransformers === false`) is handled upstream
@@ -96,8 +96,8 @@ async function runTransformers(html, config, filePath, doctype, tailwindBlocks)
96
96
  }
97
97
  let dom = parse(html);
98
98
  dom = await inlineLinkDom(dom, filePath);
99
- if (tailwindBlocks?.length) dom = await tailwindComponent(dom, tailwindBlocks, effective, filePath);
100
- dom = await tailwindcss(dom, effective, filePath);
99
+ if (tailwindBlocks?.length) dom = await tailwindComponent(dom, tailwindBlocks, effective, filePath, sourceFiles);
100
+ dom = await tailwindcss(dom, effective, filePath, sourceFiles);
101
101
  if (enabled("safeSelectors")) dom = safeSelectorsDom(dom, effective.css);
102
102
  if (enabled("attributeToStyle") && typeof effective.css?.inline === "object" && effective.css.inline.attributeToStyle) dom = attributeToStyleDom(dom, effective.css.inline.attributeToStyle);
103
103
  if (enabled("inlineCss") && effective.css?.inline) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/transformers/index.ts"],"sourcesContent":["import { parse, serialize } from '../utils/ast/index.ts'\nimport { inlineLinkDom } from './inlineLink.ts'\nimport { tailwindComponent } from './tailwindComponent.ts'\nimport { tailwindcss } from './tailwindcss.ts'\nimport { safeSelectorsDom } from './safeSelectors.ts'\nimport { attributeToStyleDom } from './attributeToStyle.ts'\nimport { inlineCssDom } from './inlineCss.ts'\nimport { msoPlaceholders } from './msoPlaceholders.ts'\nimport { columnWidth } from './columnWidth.ts'\nimport { imgWidthDom } from './imgWidth.ts'\nimport { removeAttributesDom } from './removeAttributes.ts'\nimport { shorthandCssDom } from './shorthandCss.ts'\nimport { sixHexDom } from './sixHex.ts'\nimport { addAttributesDom } from './addAttributes.ts'\nimport { filtersDom } from './filters/index.ts'\nimport { baseDom } from './base.ts'\nimport { entitiesDom } from './entities.ts'\nimport { urlQueryDom } from './urlQuery.ts'\nimport { purgeCssDom } from './purgeCss.ts'\nimport { replaceStrings } from './replaceStrings.ts'\nimport { format } from './format.ts'\nimport { minifyCodeInline } from './minifyCodeInline.ts'\nimport { minify } from './minify.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\nimport type { TailwindBlock } from '../composables/renderContext.ts'\n\n/**\n * Run all Maizzle transformers on the rendered HTML.\n *\n * The HTML is parsed into a DOM once at the start and passed through all\n * DOM-based transformers as a shared `ChildNode[]`. After all DOM transformers\n * complete, the DOM is serialized back to a string exactly once.\n *\n * String-only transformers (those that rely on external tools that require a\n * raw HTML string) then run on the serialized output.\n *\n * Transformers run in a specific order:\n * 0. Inline link stylesheets — replace `<link rel=\"stylesheet\">` with `<style>` tags\n * 1. Tailwind CSS — compile CSS, lower syntax, optimize (cleanup + merge media queries)\n * 2. Safe class names\n * 3. Attribute to style\n * 4. CSS inliner\n * 5. Remove attributes\n * 6. Shorthand CSS\n * 7. Six-digit HEX\n * 8. Add attributes\n * 9. Filters\n * 10. Base URL\n * 11. URL query\n * 11.5 Entities in comment nodes (before purge — protects MSO conditionals)\n * 12. Purge CSS (serializes/parses internally around email-comb)\n * 13. Entities\n * + Vue-generated comments stripped here (on serialized string)\n * 14. Replace strings\n * 15. Prettify\n * 16. Minify\n */\nexport async function runTransformers(\n html: string,\n config: MaizzleConfig,\n filePath?: string,\n doctype?: string,\n tailwindBlocks?: TailwindBlock[],\n): Promise<string> {\n /**\n * Per-transformer skip map — only honored when useTransformers is an object.\n * Whole-pipeline opt-out (`useTransformers === false`) is handled upstream\n * in build.ts / render so we never reach this function in that case.\n *\n * A toggle set to `true` *force-enables* its transformer for this run\n * by layering on the matching config slice (e.g. `prettify: true`\n * sets `html.format = true`). This only applies to transformers\n * whose enable flag is a plain boolean — data-driven ones\n * (filters, baseURL, urlQuery, etc.) need actual config\n * values, so a bare `true` for those is a no-op.\n */\n const toggles = typeof config.useTransformers === 'object' ? config.useTransformers : null\n const enabled = (key: keyof NonNullable<typeof toggles>) => toggles?.[key] !== false\n\n let effective = config\n if (toggles) {\n const cssOver: Record<string, unknown> = {}\n const htmlOver: Record<string, unknown> = {}\n if (toggles.inlineCss === true) cssOver.inline = true\n if (toggles.purgeCss === true) cssOver.purge = true\n if (toggles.safeSelectors === true) cssOver.safe = true\n if (toggles.shorthandCss === true) cssOver.shorthand = true\n if (toggles.sixHex === true) cssOver.sixHex = true\n if (toggles.prettify === true) htmlOver.format = true\n if (toggles.minify === true) htmlOver.minify = true\n if (toggles.entities === true) htmlOver.decodeEntities = true\n\n if (Object.keys(cssOver).length || Object.keys(htmlOver).length) {\n effective = {\n ...config,\n css: { ...config.css, ...cssOver },\n html: { ...config.html, ...htmlOver },\n }\n }\n }\n\n // Parse once — all DOM transformers share this array\n let dom = parse(html)\n\n // 0. Inline <link> stylesheets\n dom = await inlineLinkDom(dom, filePath)\n\n // 0.5. <Tailwind> component — compile per-block scoped CSS, inject into <head>\n if (tailwindBlocks?.length) {\n dom = await tailwindComponent(dom, tailwindBlocks, effective, filePath)\n }\n\n // 1. Tailwind CSS — always runs first\n dom = await tailwindcss(dom, effective, filePath)\n\n // 2. Safe class names\n if (enabled('safeSelectors')) dom = safeSelectorsDom(dom, effective.css)\n\n // 3. Attribute to style\n if (enabled('attributeToStyle') && typeof effective.css?.inline === 'object' && effective.css.inline.attributeToStyle) {\n dom = attributeToStyleDom(dom, effective.css.inline.attributeToStyle)\n }\n\n // 4. CSS inliner (serializes/parses internally around juice)\n if (enabled('inlineCss') && effective.css?.inline) {\n const inlineOptions = typeof effective.css.inline === 'object' ? effective.css.inline : {}\n dom = inlineCssDom(dom, inlineOptions)\n }\n\n // 4.5. Resolve MSO placeholders (table width + td style) from inlined CSS\n dom = msoPlaceholders(dom)\n\n // 4.6. Resolve Column min-width placeholders from nearest sized ancestor\n dom = columnWidth(dom)\n\n // 4.7. Backfill width on <Img> images that inherit their parent's width\n dom = imgWidthDom(dom)\n\n // 5. Remove attributes\n if (enabled('removeAttributes')) {\n const removeRules = effective.html?.attributes?.remove\n dom = removeAttributesDom(dom, Array.isArray(removeRules) ? removeRules : [])\n }\n\n // 6. Shorthand CSS\n if (enabled('shorthandCss') && effective.css?.shorthand) {\n const shorthandOptions = typeof effective.css.shorthand === 'object' ? effective.css.shorthand : {}\n dom = shorthandCssDom(dom, shorthandOptions)\n }\n\n // 7. Six-digit HEX\n if (enabled('sixHex') && effective.css?.sixHex !== false) dom = sixHexDom(dom)\n\n // 8. Add attributes\n if (enabled('addAttributes')) dom = addAttributesDom(dom, effective.html?.attributes)\n\n // 9. Filters\n if (enabled('filters')) dom = filtersDom(dom, effective.filters)\n\n // 10. Base URL (serializes/parses internally for VML/MSO regex passes)\n if (enabled('baseURL') && effective.url?.base) dom = baseDom(dom, effective.url.base)\n\n // 11. URL query\n if (enabled('urlQuery') && effective.url?.query && Object.keys(effective.url.query).length > 0) {\n const { _options: queryOptions, ...queryParams } = effective.url.query as Record<string, unknown>\n dom = urlQueryDom(dom, queryParams, (queryOptions ?? {}) as import('../types/config.ts').UrlQueryOptions)\n }\n\n /**\n * 11.5. Encode entities in comment nodes before purge/minify. Raw\n * invisible chars (e.g. U+00A0 from Vue decoding &nbsp; at compile\n * time) inside MSO conditionals make email-comb remove the whole\n * \"whitespace-only\" conditional and html-crush collapse the chars.\n * Text nodes are skipped here — purge's internal parse round-trip\n * would decode them again — comment data survives un-decoded.\n */\n if (enabled('entities')) dom = entitiesDom(dom, effective.html?.decodeEntities, { text: false })\n\n // 12. Remove unused CSS (serializes/parses internally around email-comb)\n if (enabled('purgeCss') && effective.css?.purge) {\n const purgeOptions = typeof effective.css.purge === 'object' ? effective.css.purge : {}\n dom = purgeCssDom(dom, purgeOptions)\n }\n\n // 13. Entities\n if (enabled('entities')) dom = entitiesDom(dom, effective.html?.decodeEntities)\n\n // Serialize once — remaining transformers operate on the HTML string\n const isXhtml = doctype ? /xhtml/i.test(doctype) : false\n let result = serialize(dom, { selfClosingTags: isXhtml })\n\n // 14. Replace strings\n if (enabled('replaceStrings')) result = replaceStrings(result, effective)\n\n // 15. Format — skipped when `minify` is enabled\n const minifyWillRun = enabled('minify') && !!effective.html?.minify\n if (enabled('prettify') && !minifyWillRun && effective.html?.format) {\n const formatOptions = typeof effective.html.format === 'object' ? effective.html.format : {}\n result = await format(result, formatOptions)\n }\n\n // 16. Minify\n if (enabled('minify') && effective.html?.minify) {\n const minifyOptions = typeof effective.html.minify === 'object' ? effective.html.minify : {}\n result = minify(result, minifyOptions)\n }\n\n /**\n * Strip self-closing slashes for HTML5 doctypes, but preserve content\n * inside MSO conditional comments (XML-ish, case/syntax sensitive).\n * MUST run BEFORE minifyCodeInline: at this point, CodeInline's\n * shiki output is still marker-encoded (§MZLT§/§MZGT§), so any\n * ` />` in the highlighted source code (e.g. a Vue self-close\n * tag) hasn't materialized yet and can't be mistakenly\n * stripped from inside a `<code>` element.\n */\n if (!isXhtml) {\n result = result.replace(\n /<!--\\[if [^\\]]*\\]>[\\s\\S]*?<!\\[endif\\]-->|( \\/>)/g,\n (match, selfClose) => selfClose ? '>' : match,\n )\n }\n\n /**\n * 16.5. Strip whitespace inside `data-minify-inline` markers (CodeInline's\n * Shiki output, etc.). Runs after format/minify so it cleans up the\n * pretty-printer's indentation between sibling tags.\n */\n result = minifyCodeInline(result)\n\n return result\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/transformers/index.ts"],"sourcesContent":["import { parse, serialize } from '../utils/ast/index.ts'\nimport { inlineLinkDom } from './inlineLink.ts'\nimport { tailwindComponent } from './tailwindComponent.ts'\nimport { tailwindcss } from './tailwindcss.ts'\nimport { safeSelectorsDom } from './safeSelectors.ts'\nimport { attributeToStyleDom } from './attributeToStyle.ts'\nimport { inlineCssDom } from './inlineCss.ts'\nimport { msoPlaceholders } from './msoPlaceholders.ts'\nimport { columnWidth } from './columnWidth.ts'\nimport { imgWidthDom } from './imgWidth.ts'\nimport { removeAttributesDom } from './removeAttributes.ts'\nimport { shorthandCssDom } from './shorthandCss.ts'\nimport { sixHexDom } from './sixHex.ts'\nimport { addAttributesDom } from './addAttributes.ts'\nimport { filtersDom } from './filters/index.ts'\nimport { baseDom } from './base.ts'\nimport { entitiesDom } from './entities.ts'\nimport { urlQueryDom } from './urlQuery.ts'\nimport { purgeCssDom } from './purgeCss.ts'\nimport { replaceStrings } from './replaceStrings.ts'\nimport { format } from './format.ts'\nimport { minifyCodeInline } from './minifyCodeInline.ts'\nimport { minify } from './minify.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\nimport type { TailwindBlock } from '../composables/renderContext.ts'\n\n/**\n * Run all Maizzle transformers on the rendered HTML.\n *\n * The HTML is parsed into a DOM once at the start and passed through all\n * DOM-based transformers as a shared `ChildNode[]`. After all DOM transformers\n * complete, the DOM is serialized back to a string exactly once.\n *\n * String-only transformers (those that rely on external tools that require a\n * raw HTML string) then run on the serialized output.\n *\n * Transformers run in a specific order:\n * 0. Inline link stylesheets — replace `<link rel=\"stylesheet\">` with `<style>` tags\n * 1. Tailwind CSS — compile CSS, lower syntax, optimize (cleanup + merge media queries)\n * 2. Safe class names\n * 3. Attribute to style\n * 4. CSS inliner\n * 5. Remove attributes\n * 6. Shorthand CSS\n * 7. Six-digit HEX\n * 8. Add attributes\n * 9. Filters\n * 10. Base URL\n * 11. URL query\n * 11.5 Entities in comment nodes (before purge — protects MSO conditionals)\n * 12. Purge CSS (serializes/parses internally around email-comb)\n * 13. Entities\n * + Vue-generated comments stripped here (on serialized string)\n * 14. Replace strings\n * 15. Prettify\n * 16. Minify\n */\nexport async function runTransformers(\n html: string,\n config: MaizzleConfig,\n filePath?: string,\n doctype?: string,\n tailwindBlocks?: TailwindBlock[],\n sourceFiles?: string[],\n): Promise<string> {\n /**\n * Per-transformer skip map — only honored when useTransformers is an object.\n * Whole-pipeline opt-out (`useTransformers === false`) is handled upstream\n * in build.ts / render so we never reach this function in that case.\n *\n * A toggle set to `true` *force-enables* its transformer for this run\n * by layering on the matching config slice (e.g. `prettify: true`\n * sets `html.format = true`). This only applies to transformers\n * whose enable flag is a plain boolean — data-driven ones\n * (filters, baseURL, urlQuery, etc.) need actual config\n * values, so a bare `true` for those is a no-op.\n */\n const toggles = typeof config.useTransformers === 'object' ? config.useTransformers : null\n const enabled = (key: keyof NonNullable<typeof toggles>) => toggles?.[key] !== false\n\n let effective = config\n if (toggles) {\n const cssOver: Record<string, unknown> = {}\n const htmlOver: Record<string, unknown> = {}\n if (toggles.inlineCss === true) cssOver.inline = true\n if (toggles.purgeCss === true) cssOver.purge = true\n if (toggles.safeSelectors === true) cssOver.safe = true\n if (toggles.shorthandCss === true) cssOver.shorthand = true\n if (toggles.sixHex === true) cssOver.sixHex = true\n if (toggles.prettify === true) htmlOver.format = true\n if (toggles.minify === true) htmlOver.minify = true\n if (toggles.entities === true) htmlOver.decodeEntities = true\n\n if (Object.keys(cssOver).length || Object.keys(htmlOver).length) {\n effective = {\n ...config,\n css: { ...config.css, ...cssOver },\n html: { ...config.html, ...htmlOver },\n }\n }\n }\n\n // Parse once — all DOM transformers share this array\n let dom = parse(html)\n\n // 0. Inline <link> stylesheets\n dom = await inlineLinkDom(dom, filePath)\n\n // 0.5. <Tailwind> component — compile per-block scoped CSS, inject into <head>\n if (tailwindBlocks?.length) {\n dom = await tailwindComponent(dom, tailwindBlocks, effective, filePath, sourceFiles)\n }\n\n // 1. Tailwind CSS — always runs first\n dom = await tailwindcss(dom, effective, filePath, sourceFiles)\n\n // 2. Safe class names\n if (enabled('safeSelectors')) dom = safeSelectorsDom(dom, effective.css)\n\n // 3. Attribute to style\n if (enabled('attributeToStyle') && typeof effective.css?.inline === 'object' && effective.css.inline.attributeToStyle) {\n dom = attributeToStyleDom(dom, effective.css.inline.attributeToStyle)\n }\n\n // 4. CSS inliner (serializes/parses internally around juice)\n if (enabled('inlineCss') && effective.css?.inline) {\n const inlineOptions = typeof effective.css.inline === 'object' ? effective.css.inline : {}\n dom = inlineCssDom(dom, inlineOptions)\n }\n\n // 4.5. Resolve MSO placeholders (table width + td style) from inlined CSS\n dom = msoPlaceholders(dom)\n\n // 4.6. Resolve Column min-width placeholders from nearest sized ancestor\n dom = columnWidth(dom)\n\n // 4.7. Backfill width on <Img> images that inherit their parent's width\n dom = imgWidthDom(dom)\n\n // 5. Remove attributes\n if (enabled('removeAttributes')) {\n const removeRules = effective.html?.attributes?.remove\n dom = removeAttributesDom(dom, Array.isArray(removeRules) ? removeRules : [])\n }\n\n // 6. Shorthand CSS\n if (enabled('shorthandCss') && effective.css?.shorthand) {\n const shorthandOptions = typeof effective.css.shorthand === 'object' ? effective.css.shorthand : {}\n dom = shorthandCssDom(dom, shorthandOptions)\n }\n\n // 7. Six-digit HEX\n if (enabled('sixHex') && effective.css?.sixHex !== false) dom = sixHexDom(dom)\n\n // 8. Add attributes\n if (enabled('addAttributes')) dom = addAttributesDom(dom, effective.html?.attributes)\n\n // 9. Filters\n if (enabled('filters')) dom = filtersDom(dom, effective.filters)\n\n // 10. Base URL (serializes/parses internally for VML/MSO regex passes)\n if (enabled('baseURL') && effective.url?.base) dom = baseDom(dom, effective.url.base)\n\n // 11. URL query\n if (enabled('urlQuery') && effective.url?.query && Object.keys(effective.url.query).length > 0) {\n const { _options: queryOptions, ...queryParams } = effective.url.query as Record<string, unknown>\n dom = urlQueryDom(dom, queryParams, (queryOptions ?? {}) as import('../types/config.ts').UrlQueryOptions)\n }\n\n /**\n * 11.5. Encode entities in comment nodes before purge/minify. Raw\n * invisible chars (e.g. U+00A0 from Vue decoding &nbsp; at compile\n * time) inside MSO conditionals make email-comb remove the whole\n * \"whitespace-only\" conditional and html-crush collapse the chars.\n * Text nodes are skipped here — purge's internal parse round-trip\n * would decode them again — comment data survives un-decoded.\n */\n if (enabled('entities')) dom = entitiesDom(dom, effective.html?.decodeEntities, { text: false })\n\n // 12. Remove unused CSS (serializes/parses internally around email-comb)\n if (enabled('purgeCss') && effective.css?.purge) {\n const purgeOptions = typeof effective.css.purge === 'object' ? effective.css.purge : {}\n dom = purgeCssDom(dom, purgeOptions)\n }\n\n // 13. Entities\n if (enabled('entities')) dom = entitiesDom(dom, effective.html?.decodeEntities)\n\n // Serialize once — remaining transformers operate on the HTML string\n const isXhtml = doctype ? /xhtml/i.test(doctype) : false\n let result = serialize(dom, { selfClosingTags: isXhtml })\n\n // 14. Replace strings\n if (enabled('replaceStrings')) result = replaceStrings(result, effective)\n\n // 15. Format — skipped when `minify` is enabled\n const minifyWillRun = enabled('minify') && !!effective.html?.minify\n if (enabled('prettify') && !minifyWillRun && effective.html?.format) {\n const formatOptions = typeof effective.html.format === 'object' ? effective.html.format : {}\n result = await format(result, formatOptions)\n }\n\n // 16. Minify\n if (enabled('minify') && effective.html?.minify) {\n const minifyOptions = typeof effective.html.minify === 'object' ? effective.html.minify : {}\n result = minify(result, minifyOptions)\n }\n\n /**\n * Strip self-closing slashes for HTML5 doctypes, but preserve content\n * inside MSO conditional comments (XML-ish, case/syntax sensitive).\n * MUST run BEFORE minifyCodeInline: at this point, CodeInline's\n * shiki output is still marker-encoded (§MZLT§/§MZGT§), so any\n * ` />` in the highlighted source code (e.g. a Vue self-close\n * tag) hasn't materialized yet and can't be mistakenly\n * stripped from inside a `<code>` element.\n */\n if (!isXhtml) {\n result = result.replace(\n /<!--\\[if [^\\]]*\\]>[\\s\\S]*?<!\\[endif\\]-->|( \\/>)/g,\n (match, selfClose) => selfClose ? '>' : match,\n )\n }\n\n /**\n * 16.5. Strip whitespace inside `data-minify-inline` markers (CodeInline's\n * Shiki output, etc.). Runs after format/minify so it cleans up the\n * pretty-printer's indentation between sibling tags.\n */\n result = minifyCodeInline(result)\n\n return result\n}\n"],"mappings":"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"}
@@ -66,7 +66,7 @@ function inlineCssDom(dom, options = {}) {
66
66
  const serialized = serialize(dom);
67
67
  let inlinedHtml;
68
68
  try {
69
- inlinedHtml = juice(serialized, {
69
+ const juiceOptions = {
70
70
  removeStyleTags: juicePassthrough.removeStyleTags ?? false,
71
71
  removeInlinedSelectors: juicePassthrough.removeInlinedSelectors ?? true,
72
72
  applyWidthAttributes: juicePassthrough.applyWidthAttributes ?? true,
@@ -75,7 +75,8 @@ function inlineCssDom(dom, options = {}) {
75
75
  ...customCSS ? { extraCss: customCSS } : {},
76
76
  inlineDuplicateProperties: juicePassthrough.inlineDuplicateProperties ?? false,
77
77
  ...juicePassthrough
78
- });
78
+ };
79
+ inlinedHtml = juice(serialized, juiceOptions);
79
80
  } catch {
80
81
  return dom;
81
82
  }
@@ -1 +1 @@
1
- {"version":3,"file":"inlineCss.js","names":[],"sources":["../../src/transformers/inlineCss.ts"],"sourcesContent":["import juice from 'juice'\nimport { walk, parse, serialize } from '../utils/ast/index.ts'\n\n/** Juice's built-in code blocks, captured before any per-call mutation. */\nconst DEFAULT_CODE_BLOCKS = { ...juice.codeBlocks }\nimport type { ChildNode, Element } from 'domhandler'\ntype JuiceOptions = NonNullable<Parameters<typeof juice>[1]>\n\n/**\n * Options for the `inlineCss` transformer. Accepts every Juice option plus a\n * handful of Maizzle-specific extras.\n */\nexport interface InlineCssOptions extends JuiceOptions {\n /**\n * Convert `0px`, `0em` etc. to `0` in inline styles.\n *\n * @default true\n */\n preferUnitlessValues?: boolean\n /**\n * CSS selectors to preserve in `<style>` tags, even after inlining.\n * Mapped to Juice's `preservedSelectors` option.\n *\n * @default []\n */\n safelist?: string[]\n /**\n * Additional CSS string to inline alongside `<style>` tag contents.\n * Mapped to Juice's `extraCss` option.\n */\n customCSS?: string\n /**\n * Duplicate CSS properties to HTML attributes.\n * Mapped to Juice's static `styleToAttribute` property.\n */\n styleToAttribute?: Record<string, string>\n /**\n * CSS properties to exclude from inlining.\n * Mapped to Juice's static `excludedProperties` property.\n */\n excludedProperties?: string[]\n /**\n * Elements that can receive `width` HTML attributes.\n * Mapped to Juice's static `widthElements` property.\n *\n * @default ['img', 'video']\n */\n widthElements?: string[]\n /**\n * Elements that can receive `height` HTML attributes.\n * Mapped to Juice's static `heightElements` property.\n *\n * @default ['img', 'video']\n */\n heightElements?: string[]\n /**\n * Template language code blocks to preserve during inlining.\n * Mapped to Juice's static `codeBlocks` property.\n */\n codeBlocks?: Record<string, { start: string; end: string }>\n}\n\n/**\n * Inline CSS from `<style>` tags into `style` attributes on matching elements.\n *\n * @param html HTML string to transform.\n * @param options Juice options plus Maizzle-specific extras.\n * @returns The transformed HTML string.\n *\n * @example\n * import { inlineCss } from '@maizzle/framework'\n *\n * const out = inlineCss('<style>.red{color:red}</style><p class=\"red\">x</p>', {\n * removeStyleTags: true,\n * })\n */\nexport function inlineCss(html: string, options: InlineCssOptions = {}): string {\n return serialize(inlineCssDom(parse(html), options))\n}\n\n/**\n * DOM-form of {@link inlineCss} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids the redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function inlineCssDom(dom: ChildNode[], options: InlineCssOptions = {}): ChildNode[] {\n const {\n preferUnitlessValues = true,\n safelist,\n customCSS = '',\n styleToAttribute,\n excludedProperties,\n widthElements,\n heightElements,\n codeBlocks,\n ...juicePassthrough\n } = options\n\n // Configure Juice static properties\n juice.styleToAttribute = styleToAttribute ?? {}\n juice.excludedProperties = ['--tw-shadow', ...(excludedProperties ?? [])]\n juice.widthElements = (widthElements ?? ['img', 'video']).map(i => i.toUpperCase()) as unknown as HTMLElement[]\n juice.heightElements = (heightElements ?? ['img', 'video']).map(i => i.toUpperCase()) as unknown as HTMLElement[]\n\n // Reset to defaults each call so a previous template's custom code blocks\n // don't accumulate on juice's module-global state, then add this call's.\n juice.codeBlocks = { ...DEFAULT_CODE_BLOCKS }\n if (codeBlocks && typeof codeBlocks === 'object') {\n Object.entries(codeBlocks).forEach(([key, value]) => {\n if (value.start && value.end) {\n juice.codeBlocks[key] = value\n }\n })\n }\n\n /**\n * Handle style tags with embed attributes. We add a marker attribute\n * that persists through the pipeline, then restore data-embed from\n * it after Juice runs. `amp-custom` (AMP4Email's CSS attribute)\n * is treated the same as embed: contents are preserved,\n * never inlined.\n */\n walk(dom, (node) => {\n const el = node as Element\n if (el.name === 'style' && el.attribs) {\n /**\n * `amp-custom` → tell juice to skip via data-embed, but don't mirror\n * back to `embed` (user wrote amp-custom, that's what stays in\n * output).\n */\n if ('amp-custom' in el.attribs && !('data-embed' in el.attribs)) {\n el.attribs['data-embed'] = ''\n }\n /**\n * Sync data-embed ↔ embed. Use `in` so presence-only attrs\n * (<style embed> → attribs.embed === '') still count.\n */\n if ('embed' in el.attribs && !('data-embed' in el.attribs)) {\n el.attribs['data-embed'] = ''\n }\n if ('data-embed' in el.attribs && !('embed' in el.attribs) && !('amp-custom' in el.attribs)) {\n el.attribs.embed = ''\n }\n\n // Add marker that persists through the pipeline\n if ('data-embed' in el.attribs) {\n el.attribs['data-maizzle-embed'] = ''\n }\n }\n })\n\n // Serialize for juice (juice requires a string)\n const serialized = serialize(dom)\n\n let inlinedHtml: string\n\n try {\n const juiceOptions: JuiceOptions = {\n removeStyleTags: juicePassthrough.removeStyleTags ?? false,\n removeInlinedSelectors: juicePassthrough.removeInlinedSelectors ?? true,\n applyWidthAttributes: juicePassthrough.applyWidthAttributes ?? true,\n applyHeightAttributes: juicePassthrough.applyHeightAttributes ?? true,\n preservedSelectors: safelist ?? [],\n ...customCSS ? { extraCss: customCSS } : {},\n inlineDuplicateProperties: juicePassthrough.inlineDuplicateProperties ?? false,\n ...juicePassthrough,\n }\n\n inlinedHtml = juice(serialized, juiceOptions)\n } catch {\n // If Juice fails, return the dom unchanged\n return dom\n }\n\n const result = parse(inlinedHtml)\n\n if (preferUnitlessValues) {\n walk(result, (node) => {\n const el = node as Element\n if (el.attribs?.style) {\n el.attribs.style = el.attribs.style.replace(\n /\\b0(px|rem|em|%|vh|vw|vmin|vmax|in|cm|mm|pt|pc|ex|ch)\\b/g,\n '0'\n )\n }\n })\n }\n\n /**\n * Restore `embed` from our marker so the purge step can detect\n * these tags and skip them. Drop `data-embed` (juice's name)\n * since it's redundant once `embed` is back, and let purge\n * strip `embed` itself at the end of its run.\n */\n walk(result, (node) => {\n const el = node as Element\n if (el.name === 'style' && el.attribs && 'data-maizzle-embed' in el.attribs) {\n /**\n * Only restore `embed` when the original signal was embed/data-embed —\n * an `amp-custom` style was tagged for juice's benefit only and\n * must not pick up a stray `embed` attribute in the rendered\n * output.\n */\n if (!('amp-custom' in el.attribs)) {\n el.attribs.embed = ''\n }\n delete el.attribs['data-embed']\n delete el.attribs['data-maizzle-embed']\n }\n })\n\n return result\n}\n"],"mappings":";;;;;;;AAIA,MAAM,sBAAsB,EAAE,GAAG,MAAM,WAAW;;;;;;;;;;;;;;;AAwElD,SAAgB,UAAU,MAAc,UAA4B,CAAC,GAAW;CAC9E,OAAO,UAAU,aAAa,MAAM,IAAI,GAAG,OAAO,CAAC;AACrD;;;;;;AAOA,SAAgB,aAAa,KAAkB,UAA4B,CAAC,GAAgB;CAC1F,MAAM,EACJ,uBAAuB,MACvB,UACA,YAAY,IACZ,kBACA,oBACA,eACA,gBACA,YACA,GAAG,qBACD;CAGJ,MAAM,mBAAmB,oBAAoB,CAAC;CAC9C,MAAM,qBAAqB,CAAC,eAAe,GAAI,sBAAsB,CAAC,CAAE;CACxE,MAAM,iBAAiB,iBAAiB,CAAC,OAAO,OAAO,EAAA,CAAG,KAAI,MAAK,EAAE,YAAY,CAAC;CAClF,MAAM,kBAAkB,kBAAkB,CAAC,OAAO,OAAO,EAAA,CAAG,KAAI,MAAK,EAAE,YAAY,CAAC;CAIpF,MAAM,aAAa,EAAE,GAAG,oBAAoB;CAC5C,IAAI,cAAc,OAAO,eAAe,UACtC,OAAO,QAAQ,UAAU,CAAC,CAAC,SAAS,CAAC,KAAK,WAAW;EACnD,IAAI,MAAM,SAAS,MAAM,KACvB,MAAM,WAAW,OAAO;CAE5B,CAAC;;;;;;;;CAUH,KAAK,MAAM,SAAS;EAClB,MAAM,KAAK;EACX,IAAI,GAAG,SAAS,WAAW,GAAG,SAAS;;;;;;GAMrC,IAAI,gBAAgB,GAAG,WAAW,EAAE,gBAAgB,GAAG,UACrD,GAAG,QAAQ,gBAAgB;;;;;GAM7B,IAAI,WAAW,GAAG,WAAW,EAAE,gBAAgB,GAAG,UAChD,GAAG,QAAQ,gBAAgB;GAE7B,IAAI,gBAAgB,GAAG,WAAW,EAAE,WAAW,GAAG,YAAY,EAAE,gBAAgB,GAAG,UACjF,GAAG,QAAQ,QAAQ;GAIrB,IAAI,gBAAgB,GAAG,SACrB,GAAG,QAAQ,wBAAwB;EAEvC;CACF,CAAC;CAGD,MAAM,aAAa,UAAU,GAAG;CAEhC,IAAI;CAEJ,IAAI;EAYF,cAAc,MAAM,YAAY;GAV9B,iBAAiB,iBAAiB,mBAAmB;GACrD,wBAAwB,iBAAiB,0BAA0B;GACnE,sBAAsB,iBAAiB,wBAAwB;GAC/D,uBAAuB,iBAAiB,yBAAyB;GACjE,oBAAoB,YAAY,CAAC;GACjC,GAAG,YAAY,EAAE,UAAU,UAAU,IAAI,CAAC;GAC1C,2BAA2B,iBAAiB,6BAA6B;GACzE,GAAG;EAGsC,CAAC;CAC9C,QAAQ;EAEN,OAAO;CACT;CAEA,MAAM,SAAS,MAAM,WAAW;CAEhC,IAAI,sBACF,KAAK,SAAS,SAAS;EACrB,MAAM,KAAK;EACX,IAAI,GAAG,SAAS,OACd,GAAG,QAAQ,QAAQ,GAAG,QAAQ,MAAM,QAClC,4DACA,GACF;CAEJ,CAAC;;;;;;;CASH,KAAK,SAAS,SAAS;EACrB,MAAM,KAAK;EACX,IAAI,GAAG,SAAS,WAAW,GAAG,WAAW,wBAAwB,GAAG,SAAS;;;;;;;GAO3E,IAAI,EAAE,gBAAgB,GAAG,UACvB,GAAG,QAAQ,QAAQ;GAErB,OAAO,GAAG,QAAQ;GAClB,OAAO,GAAG,QAAQ;EACpB;CACF,CAAC;CAED,OAAO;AACT"}
1
+ {"version":3,"file":"inlineCss.js","names":[],"sources":["../../src/transformers/inlineCss.ts"],"sourcesContent":["import juice from 'juice'\nimport { walk, parse, serialize } from '../utils/ast/index.ts'\n\n/** Juice's built-in code blocks, captured before any per-call mutation. */\nconst DEFAULT_CODE_BLOCKS = { ...juice.codeBlocks }\nimport type { ChildNode, Element } from 'domhandler'\ntype JuiceOptions = NonNullable<Parameters<typeof juice>[1]>\n\n/**\n * Options for the `inlineCss` transformer. Accepts every Juice option plus a\n * handful of Maizzle-specific extras.\n */\nexport interface InlineCssOptions extends JuiceOptions {\n /**\n * Convert `0px`, `0em` etc. to `0` in inline styles.\n *\n * @default true\n */\n preferUnitlessValues?: boolean\n /**\n * CSS selectors to preserve in `<style>` tags, even after inlining.\n * Mapped to Juice's `preservedSelectors` option.\n *\n * @default []\n */\n safelist?: string[]\n /**\n * Additional CSS string to inline alongside `<style>` tag contents.\n * Mapped to Juice's `extraCss` option.\n */\n customCSS?: string\n /**\n * Duplicate CSS properties to HTML attributes.\n * Mapped to Juice's static `styleToAttribute` property.\n */\n styleToAttribute?: Record<string, string>\n /**\n * CSS properties to exclude from inlining.\n * Mapped to Juice's static `excludedProperties` property.\n */\n excludedProperties?: string[]\n /**\n * Elements that can receive `width` HTML attributes.\n * Mapped to Juice's static `widthElements` property.\n *\n * @default ['img', 'video']\n */\n widthElements?: string[]\n /**\n * Elements that can receive `height` HTML attributes.\n * Mapped to Juice's static `heightElements` property.\n *\n * @default ['img', 'video']\n */\n heightElements?: string[]\n /**\n * Template language code blocks to preserve during inlining.\n * Mapped to Juice's static `codeBlocks` property.\n */\n codeBlocks?: Record<string, { start: string; end: string }>\n}\n\n/**\n * Inline CSS from `<style>` tags into `style` attributes on matching elements.\n *\n * @param html HTML string to transform.\n * @param options Juice options plus Maizzle-specific extras.\n * @returns The transformed HTML string.\n *\n * @example\n * import { inlineCss } from '@maizzle/framework'\n *\n * const out = inlineCss('<style>.red{color:red}</style><p class=\"red\">x</p>', {\n * removeStyleTags: true,\n * })\n */\nexport function inlineCss(html: string, options: InlineCssOptions = {}): string {\n return serialize(inlineCssDom(parse(html), options))\n}\n\n/**\n * DOM-form of {@link inlineCss} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids the redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function inlineCssDom(dom: ChildNode[], options: InlineCssOptions = {}): ChildNode[] {\n const {\n preferUnitlessValues = true,\n safelist,\n customCSS = '',\n styleToAttribute,\n excludedProperties,\n widthElements,\n heightElements,\n codeBlocks,\n ...juicePassthrough\n } = options\n\n // Configure Juice static properties\n juice.styleToAttribute = styleToAttribute ?? {}\n juice.excludedProperties = ['--tw-shadow', ...(excludedProperties ?? [])]\n juice.widthElements = (widthElements ?? ['img', 'video']).map(i => i.toUpperCase()) as unknown as HTMLElement[]\n juice.heightElements = (heightElements ?? ['img', 'video']).map(i => i.toUpperCase()) as unknown as HTMLElement[]\n\n // Reset to defaults each call so a previous template's custom code blocks\n // don't accumulate on juice's module-global state, then add this call's.\n juice.codeBlocks = { ...DEFAULT_CODE_BLOCKS }\n if (codeBlocks && typeof codeBlocks === 'object') {\n Object.entries(codeBlocks).forEach(([key, value]) => {\n if (value.start && value.end) {\n juice.codeBlocks[key] = value\n }\n })\n }\n\n /**\n * Handle style tags with embed attributes. We add a marker attribute\n * that persists through the pipeline, then restore data-embed from\n * it after Juice runs. `amp-custom` (AMP4Email's CSS attribute)\n * is treated the same as embed: contents are preserved,\n * never inlined.\n */\n walk(dom, (node) => {\n const el = node as Element\n if (el.name === 'style' && el.attribs) {\n /**\n * `amp-custom` → tell juice to skip via data-embed, but don't mirror\n * back to `embed` (user wrote amp-custom, that's what stays in\n * output).\n */\n if ('amp-custom' in el.attribs && !('data-embed' in el.attribs)) {\n el.attribs['data-embed'] = ''\n }\n /**\n * Sync data-embed ↔ embed. Use `in` so presence-only attrs\n * (<style embed> → attribs.embed === '') still count.\n */\n if ('embed' in el.attribs && !('data-embed' in el.attribs)) {\n el.attribs['data-embed'] = ''\n }\n if ('data-embed' in el.attribs && !('embed' in el.attribs) && !('amp-custom' in el.attribs)) {\n el.attribs.embed = ''\n }\n\n // Add marker that persists through the pipeline\n if ('data-embed' in el.attribs) {\n el.attribs['data-maizzle-embed'] = ''\n }\n }\n })\n\n // Serialize for juice (juice requires a string)\n const serialized = serialize(dom)\n\n let inlinedHtml: string\n\n try {\n const juiceOptions: JuiceOptions = {\n removeStyleTags: juicePassthrough.removeStyleTags ?? false,\n removeInlinedSelectors: juicePassthrough.removeInlinedSelectors ?? true,\n applyWidthAttributes: juicePassthrough.applyWidthAttributes ?? true,\n applyHeightAttributes: juicePassthrough.applyHeightAttributes ?? true,\n preservedSelectors: safelist ?? [],\n ...customCSS ? { extraCss: customCSS } : {},\n inlineDuplicateProperties: juicePassthrough.inlineDuplicateProperties ?? false,\n ...juicePassthrough,\n }\n\n inlinedHtml = juice(serialized, juiceOptions)\n } catch {\n // If Juice fails, return the dom unchanged\n return dom\n }\n\n const result = parse(inlinedHtml)\n\n if (preferUnitlessValues) {\n walk(result, (node) => {\n const el = node as Element\n if (el.attribs?.style) {\n el.attribs.style = el.attribs.style.replace(\n /\\b0(px|rem|em|%|vh|vw|vmin|vmax|in|cm|mm|pt|pc|ex|ch)\\b/g,\n '0'\n )\n }\n })\n }\n\n /**\n * Restore `embed` from our marker so the purge step can detect\n * these tags and skip them. Drop `data-embed` (juice's name)\n * since it's redundant once `embed` is back, and let purge\n * strip `embed` itself at the end of its run.\n */\n walk(result, (node) => {\n const el = node as Element\n if (el.name === 'style' && el.attribs && 'data-maizzle-embed' in el.attribs) {\n /**\n * Only restore `embed` when the original signal was embed/data-embed —\n * an `amp-custom` style was tagged for juice's benefit only and\n * must not pick up a stray `embed` attribute in the rendered\n * output.\n */\n if (!('amp-custom' in el.attribs)) {\n el.attribs.embed = ''\n }\n delete el.attribs['data-embed']\n delete el.attribs['data-maizzle-embed']\n }\n })\n\n return result\n}\n"],"mappings":";;;;;;;AAIA,MAAM,sBAAsB,EAAE,GAAG,MAAM,WAAW;;;;;;;;;;;;;;;AAwElD,SAAgB,UAAU,MAAc,UAA4B,CAAC,GAAW;CAC9E,OAAO,UAAU,aAAa,MAAM,IAAI,GAAG,OAAO,CAAC;AACrD;;;;;;AAOA,SAAgB,aAAa,KAAkB,UAA4B,CAAC,GAAgB;CAC1F,MAAM,EACJ,uBAAuB,MACvB,UACA,YAAY,IACZ,kBACA,oBACA,eACA,gBACA,YACA,GAAG,qBACD;CAGJ,MAAM,mBAAmB,oBAAoB,CAAC;CAC9C,MAAM,qBAAqB,CAAC,eAAe,GAAI,sBAAsB,CAAC,CAAE;CACxE,MAAM,iBAAiB,iBAAiB,CAAC,OAAO,OAAO,EAAA,CAAG,KAAI,MAAK,EAAE,YAAY,CAAC;CAClF,MAAM,kBAAkB,kBAAkB,CAAC,OAAO,OAAO,EAAA,CAAG,KAAI,MAAK,EAAE,YAAY,CAAC;CAIpF,MAAM,aAAa,EAAE,GAAG,oBAAoB;CAC5C,IAAI,cAAc,OAAO,eAAe,UACtC,OAAO,QAAQ,UAAU,CAAC,CAAC,SAAS,CAAC,KAAK,WAAW;EACnD,IAAI,MAAM,SAAS,MAAM,KACvB,MAAM,WAAW,OAAO;CAE5B,CAAC;;;;;;;;CAUH,KAAK,MAAM,SAAS;EAClB,MAAM,KAAK;EACX,IAAI,GAAG,SAAS,WAAW,GAAG,SAAS;;;;;;GAMrC,IAAI,gBAAgB,GAAG,WAAW,EAAE,gBAAgB,GAAG,UACrD,GAAG,QAAQ,gBAAgB;;;;;GAM7B,IAAI,WAAW,GAAG,WAAW,EAAE,gBAAgB,GAAG,UAChD,GAAG,QAAQ,gBAAgB;GAE7B,IAAI,gBAAgB,GAAG,WAAW,EAAE,WAAW,GAAG,YAAY,EAAE,gBAAgB,GAAG,UACjF,GAAG,QAAQ,QAAQ;GAIrB,IAAI,gBAAgB,GAAG,SACrB,GAAG,QAAQ,wBAAwB;EAEvC;CACF,CAAC;CAGD,MAAM,aAAa,UAAU,GAAG;CAEhC,IAAI;CAEJ,IAAI;EACF,MAAM,eAA6B;GACjC,iBAAiB,iBAAiB,mBAAmB;GACrD,wBAAwB,iBAAiB,0BAA0B;GACnE,sBAAsB,iBAAiB,wBAAwB;GAC/D,uBAAuB,iBAAiB,yBAAyB;GACjE,oBAAoB,YAAY,CAAC;GACjC,GAAG,YAAY,EAAE,UAAU,UAAU,IAAI,CAAC;GAC1C,2BAA2B,iBAAiB,6BAA6B;GACzE,GAAG;EACL;EAEA,cAAc,MAAM,YAAY,YAAY;CAC9C,QAAQ;EAEN,OAAO;CACT;CAEA,MAAM,SAAS,MAAM,WAAW;CAEhC,IAAI,sBACF,KAAK,SAAS,SAAS;EACrB,MAAM,KAAK;EACX,IAAI,GAAG,SAAS,OACd,GAAG,QAAQ,QAAQ,GAAG,QAAQ,MAAM,QAClC,4DACA,GACF;CAEJ,CAAC;;;;;;;CASH,KAAK,SAAS,SAAS;EACrB,MAAM,KAAK;EACX,IAAI,GAAG,SAAS,WAAW,GAAG,WAAW,wBAAwB,GAAG,SAAS;;;;;;;GAO3E,IAAI,EAAE,gBAAgB,GAAG,UACvB,GAAG,QAAQ,QAAQ;GAErB,OAAO,GAAG,QAAQ;GAClB,OAAO,GAAG,QAAQ;EACpB;CACF,CAAC;CAED,OAAO;AACT"}
@@ -73,7 +73,8 @@ async function inlineLinkDom(dom, filePath) {
73
73
  } else {
74
74
  if (!filePath) continue;
75
75
  try {
76
- css = readFileSync(resolve(dirname(filePath), href), "utf8");
76
+ const absolutePath = resolve(dirname(filePath), href);
77
+ css = readFileSync(absolutePath, "utf8");
77
78
  } catch {
78
79
  continue;
79
80
  }
@@ -1 +1 @@
1
- {"version":3,"file":"inlineLink.js","names":["parse"],"sources":["../../src/transformers/inlineLink.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { resolve, dirname } from 'node:path'\nimport type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize, walk } from '../utils/ast/index.ts'\n\n/**\n * Inline `<link rel=\"stylesheet\">` tags as `<style>` tags.\n *\n * - Local file paths are inlined when `filePath` is provided (resolved\n * relative to it).\n * - Remote URLs (`http://` / `https://`) are only inlined when the link\n * carries an `inline` attribute, e.g. `<link rel=\"stylesheet\" inline href=\"…\">`.\n *\n * @param html HTML string to transform.\n * @param filePath Path of the source file the HTML came from, used as the\n * base for resolving relative `href` values. Required for\n * local-file inlining; remote `inline` links work without it.\n * @returns The transformed HTML string.\n *\n * @example\n * import { inlineLink } from '@maizzle/framework'\n *\n * const out = await inlineLink(\n * '<link rel=\"stylesheet\" href=\"./styles.css\">',\n * '/path/to/template.html',\n * )\n */\nexport async function inlineLink(html: string, filePath?: string): Promise<string> {\n return serialize(await inlineLinkDom(parse(html), filePath))\n}\n\n/**\n * DOM-form of {@link inlineLink} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport async function inlineLinkDom(dom: ChildNode[], filePath?: string): Promise<ChildNode[]> {\n const links: { node: Element; parent: ChildNode; index: number }[] = []\n\n walk(dom, (node) => {\n if ((node as Element).name !== 'link') return\n\n const el = node as Element\n const attrs = el.attribs\n\n if (attrs.rel !== 'stylesheet' || !attrs.href) return\n\n const parent = el.parent as ChildNode\n\n if (parent && 'children' in parent) {\n const index = (parent.children as ChildNode[]).indexOf(el)\n if (index !== -1) {\n links.push({ node: el, parent, index })\n }\n } else {\n // Top-level node\n const index = dom.indexOf(el)\n if (index !== -1) {\n links.push({ node: el, parent: null as any, index })\n }\n }\n })\n\n for (const { node, parent, index } of links) {\n const href = node.attribs.href\n const isRemote = href.startsWith('http://') || href.startsWith('https://')\n\n let css: string | undefined\n\n if (isRemote) {\n if (!('inline' in node.attribs)) continue\n\n try {\n const response = await fetch(href)\n css = await response.text()\n } catch {\n continue\n }\n } else {\n if (!filePath) continue\n\n try {\n const absolutePath = resolve(dirname(filePath), href)\n css = readFileSync(absolutePath, 'utf8')\n } catch {\n continue\n }\n }\n\n const styleNode = {\n type: 'tag',\n name: 'style',\n attribs: {},\n children: [{\n type: 'text',\n data: css,\n parent: null as any,\n }],\n parent: parent || null,\n } as any\n\n // Set parent reference on the text child\n styleNode.children[0].parent = styleNode\n\n const siblings = parent && 'children' in parent\n ? parent.children as ChildNode[]\n : dom\n\n siblings.splice(index, 1, styleNode)\n }\n\n return dom\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,eAAsB,WAAW,MAAc,UAAoC;CACjF,OAAO,UAAU,MAAM,cAAcA,QAAM,IAAI,GAAG,QAAQ,CAAC;AAC7D;;;;;;AAOA,eAAsB,cAAc,KAAkB,UAAyC;CAC7F,MAAM,QAA+D,CAAC;CAEtE,KAAK,MAAM,SAAS;EAClB,IAAK,KAAiB,SAAS,QAAQ;EAEvC,MAAM,KAAK;EACX,MAAM,QAAQ,GAAG;EAEjB,IAAI,MAAM,QAAQ,gBAAgB,CAAC,MAAM,MAAM;EAE/C,MAAM,SAAS,GAAG;EAElB,IAAI,UAAU,cAAc,QAAQ;GAClC,MAAM,QAAS,OAAO,SAAyB,QAAQ,EAAE;GACzD,IAAI,UAAU,IACZ,MAAM,KAAK;IAAE,MAAM;IAAI;IAAQ;GAAM,CAAC;EAE1C,OAAO;GAEL,MAAM,QAAQ,IAAI,QAAQ,EAAE;GAC5B,IAAI,UAAU,IACZ,MAAM,KAAK;IAAE,MAAM;IAAI,QAAQ;IAAa;GAAM,CAAC;EAEvD;CACF,CAAC;CAED,KAAK,MAAM,EAAE,MAAM,QAAQ,WAAW,OAAO;EAC3C,MAAM,OAAO,KAAK,QAAQ;EAC1B,MAAM,WAAW,KAAK,WAAW,SAAS,KAAK,KAAK,WAAW,UAAU;EAEzE,IAAI;EAEJ,IAAI,UAAU;GACZ,IAAI,EAAE,YAAY,KAAK,UAAU;GAEjC,IAAI;IAEF,MAAM,OAAM,MADW,MAAM,IAAI,EAAA,CACZ,KAAK;GAC5B,QAAQ;IACN;GACF;EACF,OAAO;GACL,IAAI,CAAC,UAAU;GAEf,IAAI;IAEF,MAAM,aADe,QAAQ,QAAQ,QAAQ,GAAG,IAClB,GAAG,MAAM;GACzC,QAAQ;IACN;GACF;EACF;EAEA,MAAM,YAAY;GAChB,MAAM;GACN,MAAM;GACN,SAAS,CAAC;GACV,UAAU,CAAC;IACT,MAAM;IACN,MAAM;IACN,QAAQ;GACV,CAAC;GACD,QAAQ,UAAU;EACpB;EAGA,UAAU,SAAS,EAAE,CAAC,SAAS;EAM/B,CAJiB,UAAU,cAAc,SACrC,OAAO,WACP,IAAA,CAEK,OAAO,OAAO,GAAG,SAAS;CACrC;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"inlineLink.js","names":["parse"],"sources":["../../src/transformers/inlineLink.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { resolve, dirname } from 'node:path'\nimport type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize, walk } from '../utils/ast/index.ts'\n\n/**\n * Inline `<link rel=\"stylesheet\">` tags as `<style>` tags.\n *\n * - Local file paths are inlined when `filePath` is provided (resolved\n * relative to it).\n * - Remote URLs (`http://` / `https://`) are only inlined when the link\n * carries an `inline` attribute, e.g. `<link rel=\"stylesheet\" inline href=\"…\">`.\n *\n * @param html HTML string to transform.\n * @param filePath Path of the source file the HTML came from, used as the\n * base for resolving relative `href` values. Required for\n * local-file inlining; remote `inline` links work without it.\n * @returns The transformed HTML string.\n *\n * @example\n * import { inlineLink } from '@maizzle/framework'\n *\n * const out = await inlineLink(\n * '<link rel=\"stylesheet\" href=\"./styles.css\">',\n * '/path/to/template.html',\n * )\n */\nexport async function inlineLink(html: string, filePath?: string): Promise<string> {\n return serialize(await inlineLinkDom(parse(html), filePath))\n}\n\n/**\n * DOM-form of {@link inlineLink} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport async function inlineLinkDom(dom: ChildNode[], filePath?: string): Promise<ChildNode[]> {\n const links: { node: Element; parent: ChildNode; index: number }[] = []\n\n walk(dom, (node) => {\n if ((node as Element).name !== 'link') return\n\n const el = node as Element\n const attrs = el.attribs\n\n if (attrs.rel !== 'stylesheet' || !attrs.href) return\n\n const parent = el.parent as ChildNode\n\n if (parent && 'children' in parent) {\n const index = (parent.children as ChildNode[]).indexOf(el)\n if (index !== -1) {\n links.push({ node: el, parent, index })\n }\n } else {\n // Top-level node\n const index = dom.indexOf(el)\n if (index !== -1) {\n links.push({ node: el, parent: null as any, index })\n }\n }\n })\n\n for (const { node, parent, index } of links) {\n const href = node.attribs.href\n const isRemote = href.startsWith('http://') || href.startsWith('https://')\n\n let css: string | undefined\n\n if (isRemote) {\n if (!('inline' in node.attribs)) continue\n\n try {\n const response = await fetch(href)\n css = await response.text()\n } catch {\n continue\n }\n } else {\n if (!filePath) continue\n\n try {\n const absolutePath = resolve(dirname(filePath), href)\n css = readFileSync(absolutePath, 'utf8')\n } catch {\n continue\n }\n }\n\n const styleNode = {\n type: 'tag',\n name: 'style',\n attribs: {},\n children: [{\n type: 'text',\n data: css,\n parent: null as any,\n }],\n parent: parent || null,\n } as any\n\n // Set parent reference on the text child\n styleNode.children[0].parent = styleNode\n\n const siblings = parent && 'children' in parent\n ? parent.children as ChildNode[]\n : dom\n\n siblings.splice(index, 1, styleNode)\n }\n\n return dom\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,eAAsB,WAAW,MAAc,UAAoC;CACjF,OAAO,UAAU,MAAM,cAAcA,QAAM,IAAI,GAAG,QAAQ,CAAC;AAC7D;;;;;;AAOA,eAAsB,cAAc,KAAkB,UAAyC;CAC7F,MAAM,QAA+D,CAAC;CAEtE,KAAK,MAAM,SAAS;EAClB,IAAK,KAAiB,SAAS,QAAQ;EAEvC,MAAM,KAAK;EACX,MAAM,QAAQ,GAAG;EAEjB,IAAI,MAAM,QAAQ,gBAAgB,CAAC,MAAM,MAAM;EAE/C,MAAM,SAAS,GAAG;EAElB,IAAI,UAAU,cAAc,QAAQ;GAClC,MAAM,QAAS,OAAO,SAAyB,QAAQ,EAAE;GACzD,IAAI,UAAU,IACZ,MAAM,KAAK;IAAE,MAAM;IAAI;IAAQ;GAAM,CAAC;EAE1C,OAAO;GAEL,MAAM,QAAQ,IAAI,QAAQ,EAAE;GAC5B,IAAI,UAAU,IACZ,MAAM,KAAK;IAAE,MAAM;IAAI,QAAQ;IAAa;GAAM,CAAC;EAEvD;CACF,CAAC;CAED,KAAK,MAAM,EAAE,MAAM,QAAQ,WAAW,OAAO;EAC3C,MAAM,OAAO,KAAK,QAAQ;EAC1B,MAAM,WAAW,KAAK,WAAW,SAAS,KAAK,KAAK,WAAW,UAAU;EAEzE,IAAI;EAEJ,IAAI,UAAU;GACZ,IAAI,EAAE,YAAY,KAAK,UAAU;GAEjC,IAAI;IAEF,MAAM,OAAM,MADW,MAAM,IAAI,EAAA,CACZ,KAAK;GAC5B,QAAQ;IACN;GACF;EACF,OAAO;GACL,IAAI,CAAC,UAAU;GAEf,IAAI;IACF,MAAM,eAAe,QAAQ,QAAQ,QAAQ,GAAG,IAAI;IACpD,MAAM,aAAa,cAAc,MAAM;GACzC,QAAQ;IACN;GACF;EACF;EAEA,MAAM,YAAY;GAChB,MAAM;GACN,MAAM;GACN,SAAS,CAAC;GACV,UAAU,CAAC;IACT,MAAM;IACN,MAAM;IACN,QAAQ;GACV,CAAC;GACD,QAAQ,UAAU;EACpB;EAGA,UAAU,SAAS,EAAE,CAAC,SAAS;EAM/B,CAJiB,UAAU,cAAc,SACrC,OAAO,WACP,IAAA,CAEK,OAAO,OAAO,GAAG,SAAS;CACrC;CAEA,OAAO;AACT"}
@@ -17,7 +17,8 @@ const DEFAULT_OPTIONS = { removeLineBreaks: true };
17
17
  * const tight = minify('<p> hello </p>', { removeIndentations: true })
18
18
  */
19
19
  function minify(html, options = {}) {
20
- return crush(html, defu$1(options, DEFAULT_OPTIONS)).result;
20
+ const merged = defu$1(options, DEFAULT_OPTIONS);
21
+ return crush(html, merged).result;
21
22
  }
22
23
  //#endregion
23
24
  export { minify };
@@ -1 +1 @@
1
- {"version":3,"file":"minify.js","names":["merge"],"sources":["../../src/transformers/minify.ts"],"sourcesContent":["import { crush } from 'html-crush'\nimport { defu as merge } from 'defu'\nimport type { Opts as HtmlCrushOptions } from 'html-crush'\n\nexport type { Opts as MinifyOptions } from 'html-crush'\n\nconst DEFAULT_OPTIONS: Partial<HtmlCrushOptions> = {\n removeLineBreaks: true,\n}\n\n/**\n * Minify an HTML string using `html-crush`. Maizzle's only default that\n * differs from html-crush's own defaults is `removeLineBreaks: true`.\n *\n * @param html HTML string to minify.\n * @param options [html-crush options](https://codsen.com/os/html-crush) merged\n * on top of the Maizzle defaults.\n * @returns The minified HTML string.\n *\n * @example\n * import { minify } from '@maizzle/framework'\n *\n * const tight = minify('<p> hello </p>', { removeIndentations: true })\n */\nexport function minify(html: string, options: Partial<HtmlCrushOptions> = {}): string {\n const merged = merge(options, DEFAULT_OPTIONS) as Partial<HtmlCrushOptions>\n return crush(html, merged).result\n}\n"],"mappings":";;;AAMA,MAAM,kBAA6C,EACjD,kBAAkB,KACpB;;;;;;;;;;;;;;;AAgBA,SAAgB,OAAO,MAAc,UAAqC,CAAC,GAAW;CAEpF,OAAO,MAAM,MADEA,OAAM,SAAS,eACN,CAAC,CAAC,CAAC;AAC7B"}
1
+ {"version":3,"file":"minify.js","names":["merge"],"sources":["../../src/transformers/minify.ts"],"sourcesContent":["import { crush } from 'html-crush'\nimport { defu as merge } from 'defu'\nimport type { Opts as HtmlCrushOptions } from 'html-crush'\n\nexport type { Opts as MinifyOptions } from 'html-crush'\n\nconst DEFAULT_OPTIONS: Partial<HtmlCrushOptions> = {\n removeLineBreaks: true,\n}\n\n/**\n * Minify an HTML string using `html-crush`. Maizzle's only default that\n * differs from html-crush's own defaults is `removeLineBreaks: true`.\n *\n * @param html HTML string to minify.\n * @param options [html-crush options](https://codsen.com/os/html-crush) merged\n * on top of the Maizzle defaults.\n * @returns The minified HTML string.\n *\n * @example\n * import { minify } from '@maizzle/framework'\n *\n * const tight = minify('<p> hello </p>', { removeIndentations: true })\n */\nexport function minify(html: string, options: Partial<HtmlCrushOptions> = {}): string {\n const merged = merge(options, DEFAULT_OPTIONS) as Partial<HtmlCrushOptions>\n return crush(html, merged).result\n}\n"],"mappings":";;;AAMA,MAAM,kBAA6C,EACjD,kBAAkB,KACpB;;;;;;;;;;;;;;;AAgBA,SAAgB,OAAO,MAAc,UAAqC,CAAC,GAAW;CACpF,MAAM,SAASA,OAAM,SAAS,eAAe;CAC7C,OAAO,MAAM,MAAM,MAAM,CAAC,CAAC;AAC7B"}
@@ -9,7 +9,7 @@ import { ChildNode } from "domhandler";
9
9
  * One <style> per outermost block is appended to <head>; marker comments
10
10
  * are stripped after.
11
11
  */
12
- declare function tailwindComponent(dom: ChildNode[], blocks: TailwindBlock[], config: MaizzleConfig, filePath?: string): Promise<ChildNode[]>;
12
+ declare function tailwindComponent(dom: ChildNode[], blocks: TailwindBlock[], config: MaizzleConfig, filePath?: string, sourceFiles?: string[]): Promise<ChildNode[]>;
13
13
  //#endregion
14
14
  export { tailwindComponent };
15
15
  //# sourceMappingURL=tailwindComponent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tailwindComponent.d.ts","names":[],"sources":["../../src/transformers/tailwindComponent.ts"],"mappings":";;;;;;;;;;;iBA0BsB,kBACpB,KAAK,aACL,QAAQ,iBACR,QAAQ,eACR,oBACC,QAAQ"}
1
+ {"version":3,"file":"tailwindComponent.d.ts","names":[],"sources":["../../src/transformers/tailwindComponent.ts"],"mappings":";;;;;;;;;;;iBA2BsB,kBACpB,KAAK,aACL,QAAQ,iBACR,QAAQ,eACR,mBACA,yBACC,QAAQ"}
@@ -1,7 +1,8 @@
1
1
  import { walk } from "../utils/ast/walker.js";
2
2
  import "../utils/ast/index.js";
3
3
  import { compileTailwindCss } from "../utils/compileTailwindCss.js";
4
- import { resolve } from "pathe";
4
+ import { rewriteImportsSourceNone } from "./tailwindcss.js";
5
+ import { dirname, relative, resolve } from "pathe";
5
6
  //#region src/transformers/tailwindComponent.ts
6
7
  const DEFAULT_SEED = "@import \"@maizzle/tailwindcss\" source(none);";
7
8
  const OPEN_RE = /^mz-tw:(\S+)$/;
@@ -13,7 +14,7 @@ const CLOSE_RE = /^\/mz-tw:(\S+)$/;
13
14
  * One <style> per outermost block is appended to <head>; marker comments
14
15
  * are stripped after.
15
16
  */
16
- async function tailwindComponent(dom, blocks, config, filePath) {
17
+ async function tailwindComponent(dom, blocks, config, filePath, sourceFiles) {
17
18
  if (!blocks.length) return dom;
18
19
  const map = /* @__PURE__ */ new Map();
19
20
  for (const b of blocks) map.set(b.id, {
@@ -60,9 +61,14 @@ async function tailwindComponent(dom, blocks, config, filePath) {
60
61
  * the existing tailwindcss transformer out of recompiling
61
62
  * already-compiled CSS.
62
63
  */
64
+ const scoped = config.css?.scopedSources !== false && !!sourceFiles?.length;
63
65
  for (const meta of map.values()) {
64
66
  if (meta.nested) continue;
65
- const css = (await compileTailwindCss(buildCssInput(meta.configCss, meta.classes), config, `${fromPath}?tw=${meta.id}`)).trim();
67
+ const cssInput = buildCssInput(meta.configCss, meta.classes, scoped ? {
68
+ sourceFiles,
69
+ fromDir: dirname(fromPath)
70
+ } : void 0);
71
+ const css = (await compileTailwindCss(cssInput, config, `${fromPath}?tw=${meta.id}`)).trim();
66
72
  if (!css) continue;
67
73
  const styleNode = {
68
74
  type: "tag",
@@ -90,10 +96,23 @@ async function tailwindComponent(dom, blocks, config, filePath) {
90
96
  }
91
97
  return dom;
92
98
  }
93
- function buildCssInput(configCss, classes) {
94
- const seed = configCss ?? DEFAULT_SEED;
95
- if (!classes.size) return seed;
96
- return `${seed}\n@source inline("${[...classes].join(" ").replace(/"/g, "\\\"")}");`;
99
+ function buildCssInput(configCss, classes, scope) {
100
+ let seed = configCss ?? DEFAULT_SEED;
101
+ const parts = [];
102
+ /**
103
+ * Scoped mode: disable auto source detection in the user's config
104
+ * CSS and point the scanner at the template's import closure
105
+ * instead, mirroring the main tailwindcss transformer.
106
+ */
107
+ if (scope) {
108
+ seed = rewriteImportsSourceNone(seed);
109
+ for (const file of scope.sourceFiles) parts.push(`@source "${relative(scope.fromDir, file)}";`);
110
+ }
111
+ if (classes.size) {
112
+ const inline = [...classes].join(" ").replace(/"/g, "\\\"");
113
+ parts.push(`@source inline("${inline}");`);
114
+ }
115
+ return parts.length ? `${seed}\n${parts.join("\n")}` : seed;
97
116
  }
98
117
  //#endregion
99
118
  export { tailwindComponent };
@@ -1 +1 @@
1
- {"version":3,"file":"tailwindComponent.js","names":[],"sources":["../../src/transformers/tailwindComponent.ts"],"sourcesContent":["import { resolve } from 'pathe'\nimport type { ChildNode, Element, Comment } from 'domhandler'\nimport { walk } from '../utils/ast/index.ts'\nimport { compileTailwindCss } from '../utils/compileTailwindCss.ts'\nimport type { TailwindBlock } from '../composables/renderContext.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\nconst DEFAULT_SEED = '@import \"@maizzle/tailwindcss\" source(none);'\n\ninterface BlockMeta {\n id: string\n configCss?: string\n nested: boolean\n classes: Set<string>\n}\n\nconst OPEN_RE = /^mz-tw:(\\S+)$/\nconst CLOSE_RE = /^\\/mz-tw:(\\S+)$/\n\n/**\n * Compile Tailwind CSS for each top-level <Tailwind> block in the render\n * context. Nested <Tailwind> instances are flattened: their classes flow\n * up to the outermost block, their `#config` slot (if any) is ignored.\n * One <style> per outermost block is appended to <head>; marker comments\n * are stripped after.\n */\nexport async function tailwindComponent(\n dom: ChildNode[],\n blocks: TailwindBlock[],\n config: MaizzleConfig,\n filePath?: string,\n): Promise<ChildNode[]> {\n if (!blocks.length) return dom\n\n const map = new Map<string, BlockMeta>()\n for (const b of blocks) {\n map.set(b.id, { id: b.id, configCss: b.css, nested: false, classes: new Set() })\n }\n\n const stack: string[] = []\n const markers: Comment[] = []\n\n walk(dom, (node) => {\n if (node.type === 'comment') {\n const data = (node as Comment).data\n const open = data.match(OPEN_RE)\n const close = data.match(CLOSE_RE)\n if (open) {\n const id = open[1]\n const meta = map.get(id)\n if (meta && stack.length > 0) meta.nested = true\n if (meta) stack.push(id)\n markers.push(node as Comment)\n } else if (close) {\n const id = close[1]\n if (stack[stack.length - 1] === id) stack.pop()\n markers.push(node as Comment)\n }\n return\n }\n\n const el = node as Element\n /**\n * Always assign to the OUTERMOST active marker (stack[0]) so nested\n * <Tailwind> blocks merge their classes into the parent's scope.\n */\n if (el.attribs?.class && stack.length > 0) {\n map.get(stack[0])!.classes.add(el.attribs.class)\n }\n })\n\n const fromPath = filePath ?? resolve(process.cwd(), 'template.vue')\n\n let head: Element | undefined\n walk(dom, (n) => {\n if (!head && (n as Element).name === 'head') head = n as Element\n })\n\n if (!head) {\n throw new Error('`Tailwind` component requires `Head` component to be present in the template.')\n }\n\n /**\n * Compile + inject one <style raw> per outermost block. `raw` opts\n * the existing tailwindcss transformer out of recompiling\n * already-compiled CSS.\n */\n for (const meta of map.values()) {\n if (meta.nested) continue\n\n const cssInput = buildCssInput(meta.configCss, meta.classes)\n const css = (await compileTailwindCss(cssInput, config, `${fromPath}?tw=${meta.id}`)).trim()\n if (!css) continue\n\n const styleNode: Element = {\n type: 'tag',\n name: 'style',\n attribs: { raw: '' },\n children: [],\n parent: head,\n prev: null,\n next: null,\n } as any\n\n const textNode = {\n type: 'text',\n data: css,\n parent: styleNode,\n prev: null,\n next: null,\n } as any\n\n styleNode.children = [textNode]\n head.children.push(styleNode)\n }\n\n // Strip marker comments from their parents\n for (const c of markers) {\n const parent = c.parent as Element | null\n if (!parent?.children) continue\n const i = parent.children.indexOf(c)\n if (i >= 0) parent.children.splice(i, 1)\n }\n\n return dom\n}\n\nfunction buildCssInput(configCss: string | undefined, classes: Set<string>): string {\n const seed = configCss ?? DEFAULT_SEED\n\n if (!classes.size) return seed\n\n const inline = [...classes].join(' ').replace(/\"/g, '\\\\\"')\n return `${seed}\\n@source inline(\"${inline}\");`\n}\n"],"mappings":";;;;;AAOA,MAAM,eAAe;AASrB,MAAM,UAAU;AAChB,MAAM,WAAW;;;;;;;;AASjB,eAAsB,kBACpB,KACA,QACA,QACA,UACsB;CACtB,IAAI,CAAC,OAAO,QAAQ,OAAO;CAE3B,MAAM,sBAAM,IAAI,IAAuB;CACvC,KAAK,MAAM,KAAK,QACd,IAAI,IAAI,EAAE,IAAI;EAAE,IAAI,EAAE;EAAI,WAAW,EAAE;EAAK,QAAQ;EAAO,yBAAS,IAAI,IAAI;CAAE,CAAC;CAGjF,MAAM,QAAkB,CAAC;CACzB,MAAM,UAAqB,CAAC;CAE5B,KAAK,MAAM,SAAS;EAClB,IAAI,KAAK,SAAS,WAAW;GAC3B,MAAM,OAAQ,KAAiB;GAC/B,MAAM,OAAO,KAAK,MAAM,OAAO;GAC/B,MAAM,QAAQ,KAAK,MAAM,QAAQ;GACjC,IAAI,MAAM;IACR,MAAM,KAAK,KAAK;IAChB,MAAM,OAAO,IAAI,IAAI,EAAE;IACvB,IAAI,QAAQ,MAAM,SAAS,GAAG,KAAK,SAAS;IAC5C,IAAI,MAAM,MAAM,KAAK,EAAE;IACvB,QAAQ,KAAK,IAAe;GAC9B,OAAO,IAAI,OAAO;IAChB,MAAM,KAAK,MAAM;IACjB,IAAI,MAAM,MAAM,SAAS,OAAO,IAAI,MAAM,IAAI;IAC9C,QAAQ,KAAK,IAAe;GAC9B;GACA;EACF;EAEA,MAAM,KAAK;;;;;EAKX,IAAI,GAAG,SAAS,SAAS,MAAM,SAAS,GACtC,IAAI,IAAI,MAAM,EAAE,CAAC,CAAE,QAAQ,IAAI,GAAG,QAAQ,KAAK;CAEnD,CAAC;CAED,MAAM,WAAW,YAAY,QAAQ,QAAQ,IAAI,GAAG,cAAc;CAElE,IAAI;CACJ,KAAK,MAAM,MAAM;EACf,IAAI,CAAC,QAAS,EAAc,SAAS,QAAQ,OAAO;CACtD,CAAC;CAED,IAAI,CAAC,MACH,MAAM,IAAI,MAAM,+EAA+E;;;;;;CAQjG,KAAK,MAAM,QAAQ,IAAI,OAAO,GAAG;EAC/B,IAAI,KAAK,QAAQ;EAGjB,MAAM,OAAO,MAAM,mBADF,cAAc,KAAK,WAAW,KAAK,OACP,GAAG,QAAQ,GAAG,SAAS,MAAM,KAAK,IAAI,EAAA,CAAG,KAAK;EAC3F,IAAI,CAAC,KAAK;EAEV,MAAM,YAAqB;GACzB,MAAM;GACN,MAAM;GACN,SAAS,EAAE,KAAK,GAAG;GACnB,UAAU,CAAC;GACX,QAAQ;GACR,MAAM;GACN,MAAM;EACR;EAUA,UAAU,WAAW,CAAC;GAPpB,MAAM;GACN,MAAM;GACN,QAAQ;GACR,MAAM;GACN,MAAM;EAGqB,CAAC;EAC9B,KAAK,SAAS,KAAK,SAAS;CAC9B;CAGA,KAAK,MAAM,KAAK,SAAS;EACvB,MAAM,SAAS,EAAE;EACjB,IAAI,CAAC,QAAQ,UAAU;EACvB,MAAM,IAAI,OAAO,SAAS,QAAQ,CAAC;EACnC,IAAI,KAAK,GAAG,OAAO,SAAS,OAAO,GAAG,CAAC;CACzC;CAEA,OAAO;AACT;AAEA,SAAS,cAAc,WAA+B,SAA8B;CAClF,MAAM,OAAO,aAAa;CAE1B,IAAI,CAAC,QAAQ,MAAM,OAAO;CAG1B,OAAO,GAAG,KAAK,oBADA,CAAC,GAAG,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,QAAQ,MAAM,MACZ,EAAE;AAC5C"}
1
+ {"version":3,"file":"tailwindComponent.js","names":[],"sources":["../../src/transformers/tailwindComponent.ts"],"sourcesContent":["import { resolve, relative, dirname } from 'pathe'\nimport type { ChildNode, Element, Comment } from 'domhandler'\nimport { walk } from '../utils/ast/index.ts'\nimport { rewriteImportsSourceNone } from './tailwindcss.ts'\nimport { compileTailwindCss } from '../utils/compileTailwindCss.ts'\nimport type { TailwindBlock } from '../composables/renderContext.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\nconst DEFAULT_SEED = '@import \"@maizzle/tailwindcss\" source(none);'\n\ninterface BlockMeta {\n id: string\n configCss?: string\n nested: boolean\n classes: Set<string>\n}\n\nconst OPEN_RE = /^mz-tw:(\\S+)$/\nconst CLOSE_RE = /^\\/mz-tw:(\\S+)$/\n\n/**\n * Compile Tailwind CSS for each top-level <Tailwind> block in the render\n * context. Nested <Tailwind> instances are flattened: their classes flow\n * up to the outermost block, their `#config` slot (if any) is ignored.\n * One <style> per outermost block is appended to <head>; marker comments\n * are stripped after.\n */\nexport async function tailwindComponent(\n dom: ChildNode[],\n blocks: TailwindBlock[],\n config: MaizzleConfig,\n filePath?: string,\n sourceFiles?: string[],\n): Promise<ChildNode[]> {\n if (!blocks.length) return dom\n\n const map = new Map<string, BlockMeta>()\n for (const b of blocks) {\n map.set(b.id, { id: b.id, configCss: b.css, nested: false, classes: new Set() })\n }\n\n const stack: string[] = []\n const markers: Comment[] = []\n\n walk(dom, (node) => {\n if (node.type === 'comment') {\n const data = (node as Comment).data\n const open = data.match(OPEN_RE)\n const close = data.match(CLOSE_RE)\n if (open) {\n const id = open[1]\n const meta = map.get(id)\n if (meta && stack.length > 0) meta.nested = true\n if (meta) stack.push(id)\n markers.push(node as Comment)\n } else if (close) {\n const id = close[1]\n if (stack[stack.length - 1] === id) stack.pop()\n markers.push(node as Comment)\n }\n return\n }\n\n const el = node as Element\n /**\n * Always assign to the OUTERMOST active marker (stack[0]) so nested\n * <Tailwind> blocks merge their classes into the parent's scope.\n */\n if (el.attribs?.class && stack.length > 0) {\n map.get(stack[0])!.classes.add(el.attribs.class)\n }\n })\n\n const fromPath = filePath ?? resolve(process.cwd(), 'template.vue')\n\n let head: Element | undefined\n walk(dom, (n) => {\n if (!head && (n as Element).name === 'head') head = n as Element\n })\n\n if (!head) {\n throw new Error('`Tailwind` component requires `Head` component to be present in the template.')\n }\n\n /**\n * Compile + inject one <style raw> per outermost block. `raw` opts\n * the existing tailwindcss transformer out of recompiling\n * already-compiled CSS.\n */\n const scoped = config.css?.scopedSources !== false && !!sourceFiles?.length\n\n for (const meta of map.values()) {\n if (meta.nested) continue\n\n const cssInput = buildCssInput(meta.configCss, meta.classes, scoped ? { sourceFiles: sourceFiles!, fromDir: dirname(fromPath) } : undefined)\n const css = (await compileTailwindCss(cssInput, config, `${fromPath}?tw=${meta.id}`)).trim()\n if (!css) continue\n\n const styleNode: Element = {\n type: 'tag',\n name: 'style',\n attribs: { raw: '' },\n children: [],\n parent: head,\n prev: null,\n next: null,\n } as any\n\n const textNode = {\n type: 'text',\n data: css,\n parent: styleNode,\n prev: null,\n next: null,\n } as any\n\n styleNode.children = [textNode]\n head.children.push(styleNode)\n }\n\n // Strip marker comments from their parents\n for (const c of markers) {\n const parent = c.parent as Element | null\n if (!parent?.children) continue\n const i = parent.children.indexOf(c)\n if (i >= 0) parent.children.splice(i, 1)\n }\n\n return dom\n}\n\nfunction buildCssInput(\n configCss: string | undefined,\n classes: Set<string>,\n scope?: { sourceFiles: string[]; fromDir: string },\n): string {\n let seed = configCss ?? DEFAULT_SEED\n\n const parts: string[] = []\n\n /**\n * Scoped mode: disable auto source detection in the user's config\n * CSS and point the scanner at the template's import closure\n * instead, mirroring the main tailwindcss transformer.\n */\n if (scope) {\n seed = rewriteImportsSourceNone(seed)\n for (const file of scope.sourceFiles) {\n parts.push(`@source \"${relative(scope.fromDir, file)}\";`)\n }\n }\n\n if (classes.size) {\n const inline = [...classes].join(' ').replace(/\"/g, '\\\\\"')\n parts.push(`@source inline(\"${inline}\");`)\n }\n\n return parts.length ? `${seed}\\n${parts.join('\\n')}` : seed\n}\n"],"mappings":"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"}
@@ -1,6 +1,12 @@
1
1
  import { MaizzleConfig } from "../types/config.js";
2
2
  import { ChildNode } from "domhandler";
3
3
  //#region src/transformers/tailwindcss.d.ts
4
+ /**
5
+ * Append ` source(none)` to Tailwind imports so auto source detection
6
+ * is off and only our explicit `@source` directives apply. Skips
7
+ * imports that already carry a `source(...)` modifier.
8
+ */
9
+ declare function rewriteImportsSourceNone(css: string): string;
4
10
  /**
5
11
  * Tailwind CSS transformer.
6
12
  *
@@ -9,7 +15,8 @@ import { ChildNode } from "domhandler";
9
15
  *
10
16
  * Configures Tailwind sources to scan:
11
17
  * - Rendered class attributes (via `@source inline`) for all classes from all components
12
- * - User project files (via Tailwind's auto-detection from base/from path)
18
+ * - The template's module import closure (via `@source` directives), or user
19
+ * project files (via Tailwind's auto-detection) when `css.scopedSources` is off
13
20
  *
14
21
  * User `@source` and `@source not directives` in style tags are preserved.
15
22
  * Source directives are only added to style tags that import Tailwind.
@@ -17,7 +24,7 @@ import { ChildNode } from "domhandler";
17
24
  * Runs as the first transformer in the pipeline so that subsequent
18
25
  * transformers (inliner, purge, etc.) work with fully compiled CSS.
19
26
  */
20
- declare function tailwindcss(dom: ChildNode[], config: MaizzleConfig, filePath?: string): Promise<ChildNode[]>;
27
+ declare function tailwindcss(dom: ChildNode[], config: MaizzleConfig, filePath?: string, sourceFiles?: string[]): Promise<ChildNode[]>;
21
28
  //#endregion
22
- export { tailwindcss };
29
+ export { rewriteImportsSourceNone, tailwindcss };
23
30
  //# sourceMappingURL=tailwindcss.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tailwindcss.d.ts","names":[],"sources":["../../src/transformers/tailwindcss.ts"],"mappings":";;;;;;;;;;;;;;;;;;;iBA4JsB,YAAY,KAAK,aAAa,QAAQ,eAAe,oBAAoB,QAAQ"}
1
+ {"version":3,"file":"tailwindcss.d.ts","names":[],"sources":["../../src/transformers/tailwindcss.ts"],"mappings":";;;;;;;;iBAgKgB,yBAAyB;;;;;;;;;;;;;;;;;;iBAwBnB,YAAY,KAAK,aAAa,QAAQ,eAAe,mBAAmB,yBAAyB,QAAQ"}
@@ -1,7 +1,7 @@
1
1
  import { walk } from "../utils/ast/walker.js";
2
2
  import "../utils/ast/index.js";
3
- import { compileTailwindCss } from "../utils/compileTailwindCss.js";
4
3
  import { decodeStyleEntities } from "../utils/decodeStyleEntities.js";
4
+ import { compileTailwindCss } from "../utils/compileTailwindCss.js";
5
5
  import { dirname, relative, resolve } from "pathe";
6
6
  //#region src/transformers/tailwindcss.ts
7
7
  /**
@@ -24,10 +24,21 @@ function usesTailwind(css) {
24
24
  * expressions, and the template itself — Tailwind's scanner handles
25
25
  * the actual class extraction from these raw values
26
26
  */
27
- function buildSourceDirectives(dom, config, fromDir) {
27
+ function buildSourceDirectives(dom, config, fromDir, sourceFiles) {
28
28
  const directives = [];
29
- const excludePaths = [resolve(config.output?.path ?? "dist"), ...(config.css?.exclude ?? []).map((p) => resolve(p))];
30
- for (const p of excludePaths) directives.push(`@source not "${relative(fromDir, resolve(p))}";`);
29
+ if (config.css?.scopedSources !== false && !!sourceFiles?.length)
30
+ /**
31
+ * Scoped mode: point Tailwind's scanner at exactly the files in
32
+ * this template's import closure instead of auto-detecting from
33
+ * the project root. Auto-detection is disabled by appending
34
+ * `source(none)` to the Tailwind import (see
35
+ * rewriteImportsSourceNone below).
36
+ */
37
+ for (const file of sourceFiles) directives.push(`@source "${relative(fromDir, file)}";`);
38
+ else {
39
+ const excludePaths = [resolve(config.output?.path ?? "dist"), ...(config.css?.exclude ?? []).map((p) => resolve(p))];
40
+ for (const p of excludePaths) directives.push(`@source not "${relative(fromDir, resolve(p))}";`);
41
+ }
31
42
  /**
32
43
  * Inline source: collect all class attribute values from the rendered DOM.
33
44
  * After Vue SSR, the DOM contains every class from every component
@@ -104,6 +115,14 @@ function collectGradientCombos(dom) {
104
115
  return [...bySignature.values()];
105
116
  }
106
117
  /**
118
+ * Append ` source(none)` to Tailwind imports so auto source detection
119
+ * is off and only our explicit `@source` directives apply. Skips
120
+ * imports that already carry a `source(...)` modifier.
121
+ */
122
+ function rewriteImportsSourceNone(css) {
123
+ return css.replace(/(@import\s+["'](?:@maizzle\/)?tailwindcss(?:\/[^"']*)?["'][^;]*);/g, (match, stmt) => stmt.includes("source(") ? match : `${stmt} source(none);`);
124
+ }
125
+ /**
107
126
  * Tailwind CSS transformer.
108
127
  *
109
128
  * Compiles CSS inside <style> tags in the DOM using
@@ -111,7 +130,8 @@ function collectGradientCombos(dom) {
111
130
  *
112
131
  * Configures Tailwind sources to scan:
113
132
  * - Rendered class attributes (via `@source inline`) for all classes from all components
114
- * - User project files (via Tailwind's auto-detection from base/from path)
133
+ * - The template's module import closure (via `@source` directives), or user
134
+ * project files (via Tailwind's auto-detection) when `css.scopedSources` is off
115
135
  *
116
136
  * User `@source` and `@source not directives` in style tags are preserved.
117
137
  * Source directives are only added to style tags that import Tailwind.
@@ -119,7 +139,7 @@ function collectGradientCombos(dom) {
119
139
  * Runs as the first transformer in the pipeline so that subsequent
120
140
  * transformers (inliner, purge, etc.) work with fully compiled CSS.
121
141
  */
122
- async function tailwindcss(dom, config, filePath) {
142
+ async function tailwindcss(dom, config, filePath, sourceFiles) {
123
143
  const styleTags = [];
124
144
  walk(dom, (node) => {
125
145
  if (node.name !== "style") return;
@@ -145,7 +165,8 @@ async function tailwindcss(dom, config, filePath) {
145
165
  const fromPath = filePath ?? resolve(process.cwd(), "template.vue");
146
166
  const fromDir = dirname(fromPath);
147
167
  const hasTailwindStyles = styleTags.some(({ cssContent }) => usesTailwind(cssContent));
148
- const sourceDirectives = hasTailwindStyles ? buildSourceDirectives(dom, config, fromDir) : "";
168
+ const sourceDirectives = hasTailwindStyles ? buildSourceDirectives(dom, config, fromDir, sourceFiles) : "";
169
+ const scoped = config.css?.scopedSources !== false && !!sourceFiles?.length;
149
170
  /**
150
171
  * Collect gradient combos and rewrite elements to single classes.
151
172
  * Runs after source directives are built (so the utility classes are
@@ -156,12 +177,7 @@ async function tailwindcss(dom, config, filePath) {
156
177
  const firstTailwindStyle = styleTags.findIndex(({ cssContent }) => usesTailwind(cssContent));
157
178
  for (let i = 0; i < styleTags.length; i++) {
158
179
  const { node, cssContent } = styleTags[i];
159
- /**
160
- * Only add source directives to style tags that import Tailwind —
161
- * plain CSS doesn't need them and @tailwindcss/postcss would
162
- * leave the directives unconsumed in the output.
163
- */
164
- const fullCss = usesTailwind(cssContent) ? `${cssContent}\n${sourceDirectives}` : cssContent;
180
+ const fullCss = usesTailwind(cssContent) ? `${scoped ? rewriteImportsSourceNone(cssContent) : cssContent}\n${sourceDirectives}` : cssContent;
165
181
  const combos = i === firstTailwindStyle ? gradientCombos : [];
166
182
  try {
167
183
  node.children = [{
@@ -184,6 +200,6 @@ async function tailwindcss(dom, config, filePath) {
184
200
  return dom;
185
201
  }
186
202
  //#endregion
187
- export { tailwindcss };
203
+ export { rewriteImportsSourceNone, tailwindcss };
188
204
 
189
205
  //# sourceMappingURL=tailwindcss.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tailwindcss.js","names":[],"sources":["../../src/transformers/tailwindcss.ts"],"sourcesContent":["import { resolve, dirname, relative } from 'pathe'\nimport type { ChildNode, Element } from 'domhandler'\nimport { walk } from '../utils/ast/index.ts'\nimport { decodeStyleEntities } from '../utils/decodeStyleEntities.ts'\nimport { compileTailwindCss } from '../utils/compileTailwindCss.ts'\nimport type { GradientCombo } from '../plugins/postcss/flattenGradients.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\n/**\n * Check if CSS content uses Tailwind features that require source scanning.\n *\n * Only CSS that imports Tailwind (or @maizzle/tailwindcss) needs @source\n * directives. Plain CSS without Tailwind imports doesn't need scanning\n * and would pass through @source directives unconsumed.\n */\nfunction usesTailwind(css: string): boolean {\n return /((@import|@reference)\\s+[\"'](tailwindcss|@maizzle\\/tailwindcss)|@tailwind\\s)/.test(css)\n}\n\n/**\n * Build @source directives for Tailwind CSS scanning.\n *\n * Configures two types of sources:\n * 1. Exclusions for output dir and user-configured paths\n * 2. Inline source with all class attribute values from the rendered DOM,\n * capturing classes from all components (built-in + user), dynamic\n * expressions, and the template itself — Tailwind's scanner handles\n * the actual class extraction from these raw values\n */\nfunction buildSourceDirectives(dom: ChildNode[], config: MaizzleConfig, fromDir: string): string {\n const directives: string[] = []\n\n // Exclude output dir and user-configured paths\n const excludePaths = [\n resolve(config.output?.path ?? 'dist'),\n ...(config.css?.exclude ?? []).map(p => resolve(p)),\n ]\n\n for (const p of excludePaths) {\n directives.push(`@source not \"${relative(fromDir, resolve(p))}\";`)\n }\n\n /**\n * Inline source: collect all class attribute values from the rendered DOM.\n * After Vue SSR, the DOM contains every class from every component\n * (built-in framework components, user components, dynamic\n * bindings). We pass these raw values to Tailwind's\n * scanner via @source inline().\n */\n const classes: string[] = []\n walk(dom, (n) => {\n const cls = (n as Element).attribs?.class\n if (cls) classes.push(cls)\n })\n\n if (classes.length) {\n directives.push(`@source inline(\"${classes.join(' ')}\");`)\n }\n\n return directives.join('\\n')\n}\n\nconst GRADIENT_FN_RE = /^bg-(linear|radial|conic)\\b/\nconst GRADIENT_GENERATED_RE = /^bg-(linear|radial|conic)-gradient-/\nconst GRADIENT_STOP_RE = /^(from|via|to)-/\n\n/** Rank a stop class so generated names are stable regardless of author order. */\nfunction stopRank(cls: string): number {\n const prefix = cls.startsWith('from-') ? 0 : cls.startsWith('via-') ? 1 : 2\n const isPosition = /^(from|via|to)-\\d+%$/.test(cls) ? 1 : 0\n return prefix * 2 + isPosition\n}\n\n/** Sanitize a class token into a valid, readable CSS class name fragment. */\nfunction sanitize(token: string): string {\n return token\n .replace(/[[\\]#()]/g, '')\n .replace(/[/,.%\\s]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n}\n\n/**\n * Detect Tailwind gradient class combinations on DOM elements.\n *\n * A gradient only works once its `bg-linear/radial/conic` direction class\n * combines with `from-*`/`via-*`/`to-*` stops on the same element. This\n * collects those per-element combos, rewrites each element to a single\n * readable class (e.g. `bg-linear-gradient-to-bl-from-indigo-50-to-indigo-600`),\n * and returns the combos so the flattenGradients plugin can emit one flat\n * `background-image` rule per unique combo.\n */\nfunction collectGradientCombos(dom: ChildNode[]): GradientCombo[] {\n const bySignature = new Map<string, GradientCombo>()\n const usedNames = new Map<string, string>()\n\n walk(dom, (node) => {\n const el = node as Element\n const cls = el.attribs?.class\n if (!cls) return\n\n const tokens = cls.split(/\\s+/).filter(Boolean)\n const fnClass = tokens.find(t => GRADIENT_FN_RE.test(t) && !GRADIENT_GENERATED_RE.test(t))\n if (!fnClass) return\n\n const gradientClasses = tokens.filter(\n t => (GRADIENT_FN_RE.test(t) && !GRADIENT_GENERATED_RE.test(t)) || GRADIENT_STOP_RE.test(t),\n )\n const stops = gradientClasses.filter(t => t !== fnClass).sort((a, b) => stopRank(a) - stopRank(b) || a.localeCompare(b))\n const ordered = [fnClass, ...stops]\n const signature = ordered.join(' ')\n\n let combo = bySignature.get(signature)\n if (!combo) {\n const fn = fnClass.match(GRADIENT_FN_RE)![1]\n const dirRemainder = fnClass.replace(new RegExp(`^bg-${fn}-?`), '')\n const parts = [dirRemainder, ...stops].filter(Boolean).map(sanitize)\n let name = `bg-${fn}-gradient${parts.length ? `-${parts.join('-')}` : ''}`\n\n // Guard against sanitize collisions from distinct combos.\n const existing = usedNames.get(name)\n if (existing && existing !== signature) {\n let n = 2\n while (usedNames.has(`${name}-${n}`)) n++\n name = `${name}-${n}`\n }\n usedNames.set(name, signature)\n\n combo = { className: name, classes: ordered }\n bySignature.set(signature, combo)\n }\n\n // Replace the gradient utilities with the single generated class.\n const rest = tokens.filter(t => !gradientClasses.includes(t))\n el.attribs.class = [...rest, combo.className].join(' ')\n })\n\n return [...bySignature.values()]\n}\n\n/**\n * Tailwind CSS transformer.\n *\n * Compiles CSS inside <style> tags in the DOM using\n * @tailwindcss/postcss, then lowers modern CSS syntax with lightningcss.\n *\n * Configures Tailwind sources to scan:\n * - Rendered class attributes (via `@source inline`) for all classes from all components\n * - User project files (via Tailwind's auto-detection from base/from path)\n *\n * User `@source` and `@source not directives` in style tags are preserved.\n * Source directives are only added to style tags that import Tailwind.\n *\n * Runs as the first transformer in the pipeline so that subsequent\n * transformers (inliner, purge, etc.) work with fully compiled CSS.\n */\nexport async function tailwindcss(dom: ChildNode[], config: MaizzleConfig, filePath?: string): Promise<ChildNode[]> {\n const styleTags: { node: Element; cssContent: string }[] = []\n\n walk(dom, (node) => {\n if ((node as Element).name !== 'style') return\n\n const el = node as Element\n const attrs = el.attribs\n\n /**\n * `raw` opts out of compilation entirely (marker is consumed here).\n * `embed`/`data-embed` only signal \"preserve tag after inlining\"\n * — they still need to go through compile so Tailwind/@apply\n * resolves.\n */\n if ('raw' in attrs) {\n delete el.attribs.raw\n return\n }\n\n // Get text content from children and decode HTML entities\n const rawContent = el.children\n .filter(child => child.type === 'text')\n .map(child => (child as any).data)\n .join('')\n\n if (!rawContent.trim()) return\n\n styleTags.push({ node: el, cssContent: decodeStyleEntities(rawContent) })\n })\n\n if (!styleTags.length) return dom\n\n const fromPath = filePath ?? resolve(process.cwd(), 'template.vue')\n const fromDir = dirname(fromPath)\n\n // Only compute source directives if at least one style tag uses Tailwind\n const hasTailwindStyles = styleTags.some(({ cssContent }) => usesTailwind(cssContent))\n const sourceDirectives = hasTailwindStyles\n ? buildSourceDirectives(dom, config, fromDir)\n : ''\n\n /**\n * Collect gradient combos and rewrite elements to single classes.\n * Runs after source directives are built (so the utility classes are\n * still scanned) and only feeds the first Tailwind style tag, whose\n * `:root` holds the theme colors the flat rules reference.\n */\n const gradientCombos = hasTailwindStyles ? collectGradientCombos(dom) : []\n const firstTailwindStyle = styleTags.findIndex(({ cssContent }) => usesTailwind(cssContent))\n\n for (let i = 0; i < styleTags.length; i++) {\n const { node, cssContent } = styleTags[i]\n\n /**\n * Only add source directives to style tags that import Tailwind —\n * plain CSS doesn't need them and @tailwindcss/postcss would\n * leave the directives unconsumed in the output.\n */\n const fullCss = usesTailwind(cssContent)\n ? `${cssContent}\\n${sourceDirectives}`\n : cssContent\n\n const combos = i === firstTailwindStyle ? gradientCombos : []\n\n try {\n const optimized = await compileTailwindCss(fullCss, config, `${fromPath}?style=${i}`, combos)\n\n // Replace the style tag's children with the compiled CSS\n node.children = [{\n type: 'text',\n data: optimized,\n parent: node,\n } as any]\n } catch {\n /**\n * If CSS processing fails, still replace with decoded content\n * so HTML entities don't break the CSS.\n */\n node.children = [{\n type: 'text',\n data: cssContent,\n parent: node,\n } as any]\n }\n }\n\n return dom\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tailwindcss.js","names":[],"sources":["../../src/transformers/tailwindcss.ts"],"sourcesContent":["import { resolve, dirname, relative } from 'pathe'\nimport type { ChildNode, Element } from 'domhandler'\nimport { walk } from '../utils/ast/index.ts'\nimport { decodeStyleEntities } from '../utils/decodeStyleEntities.ts'\nimport { compileTailwindCss } from '../utils/compileTailwindCss.ts'\nimport type { GradientCombo } from '../plugins/postcss/flattenGradients.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\n/**\n * Check if CSS content uses Tailwind features that require source scanning.\n *\n * Only CSS that imports Tailwind (or @maizzle/tailwindcss) needs @source\n * directives. Plain CSS without Tailwind imports doesn't need scanning\n * and would pass through @source directives unconsumed.\n */\nfunction usesTailwind(css: string): boolean {\n return /((@import|@reference)\\s+[\"'](tailwindcss|@maizzle\\/tailwindcss)|@tailwind\\s)/.test(css)\n}\n\n/**\n * Build @source directives for Tailwind CSS scanning.\n *\n * Configures two types of sources:\n * 1. Exclusions for output dir and user-configured paths\n * 2. Inline source with all class attribute values from the rendered DOM,\n * capturing classes from all components (built-in + user), dynamic\n * expressions, and the template itself — Tailwind's scanner handles\n * the actual class extraction from these raw values\n */\nfunction buildSourceDirectives(dom: ChildNode[], config: MaizzleConfig, fromDir: string, sourceFiles?: string[]): string {\n const directives: string[] = []\n\n const scoped = config.css?.scopedSources !== false && !!sourceFiles?.length\n\n if (scoped) {\n /**\n * Scoped mode: point Tailwind's scanner at exactly the files in\n * this template's import closure instead of auto-detecting from\n * the project root. Auto-detection is disabled by appending\n * `source(none)` to the Tailwind import (see\n * rewriteImportsSourceNone below).\n */\n for (const file of sourceFiles!) {\n directives.push(`@source \"${relative(fromDir, file)}\";`)\n }\n } else {\n // Exclude output dir and user-configured paths\n const excludePaths = [\n resolve(config.output?.path ?? 'dist'),\n ...(config.css?.exclude ?? []).map(p => resolve(p)),\n ]\n\n for (const p of excludePaths) {\n directives.push(`@source not \"${relative(fromDir, resolve(p))}\";`)\n }\n }\n\n /**\n * Inline source: collect all class attribute values from the rendered DOM.\n * After Vue SSR, the DOM contains every class from every component\n * (built-in framework components, user components, dynamic\n * bindings). We pass these raw values to Tailwind's\n * scanner via @source inline().\n */\n const classes: string[] = []\n walk(dom, (n) => {\n const cls = (n as Element).attribs?.class\n if (cls) classes.push(cls)\n })\n\n if (classes.length) {\n directives.push(`@source inline(\"${classes.join(' ')}\");`)\n }\n\n return directives.join('\\n')\n}\n\nconst GRADIENT_FN_RE = /^bg-(linear|radial|conic)\\b/\nconst GRADIENT_GENERATED_RE = /^bg-(linear|radial|conic)-gradient-/\nconst GRADIENT_STOP_RE = /^(from|via|to)-/\n\n/** Rank a stop class so generated names are stable regardless of author order. */\nfunction stopRank(cls: string): number {\n const prefix = cls.startsWith('from-') ? 0 : cls.startsWith('via-') ? 1 : 2\n const isPosition = /^(from|via|to)-\\d+%$/.test(cls) ? 1 : 0\n return prefix * 2 + isPosition\n}\n\n/** Sanitize a class token into a valid, readable CSS class name fragment. */\nfunction sanitize(token: string): string {\n return token\n .replace(/[[\\]#()]/g, '')\n .replace(/[/,.%\\s]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n}\n\n/**\n * Detect Tailwind gradient class combinations on DOM elements.\n *\n * A gradient only works once its `bg-linear/radial/conic` direction class\n * combines with `from-*`/`via-*`/`to-*` stops on the same element. This\n * collects those per-element combos, rewrites each element to a single\n * readable class (e.g. `bg-linear-gradient-to-bl-from-indigo-50-to-indigo-600`),\n * and returns the combos so the flattenGradients plugin can emit one flat\n * `background-image` rule per unique combo.\n */\nfunction collectGradientCombos(dom: ChildNode[]): GradientCombo[] {\n const bySignature = new Map<string, GradientCombo>()\n const usedNames = new Map<string, string>()\n\n walk(dom, (node) => {\n const el = node as Element\n const cls = el.attribs?.class\n if (!cls) return\n\n const tokens = cls.split(/\\s+/).filter(Boolean)\n const fnClass = tokens.find(t => GRADIENT_FN_RE.test(t) && !GRADIENT_GENERATED_RE.test(t))\n if (!fnClass) return\n\n const gradientClasses = tokens.filter(\n t => (GRADIENT_FN_RE.test(t) && !GRADIENT_GENERATED_RE.test(t)) || GRADIENT_STOP_RE.test(t),\n )\n const stops = gradientClasses.filter(t => t !== fnClass).sort((a, b) => stopRank(a) - stopRank(b) || a.localeCompare(b))\n const ordered = [fnClass, ...stops]\n const signature = ordered.join(' ')\n\n let combo = bySignature.get(signature)\n if (!combo) {\n const fn = fnClass.match(GRADIENT_FN_RE)![1]\n const dirRemainder = fnClass.replace(new RegExp(`^bg-${fn}-?`), '')\n const parts = [dirRemainder, ...stops].filter(Boolean).map(sanitize)\n let name = `bg-${fn}-gradient${parts.length ? `-${parts.join('-')}` : ''}`\n\n // Guard against sanitize collisions from distinct combos.\n const existing = usedNames.get(name)\n if (existing && existing !== signature) {\n let n = 2\n while (usedNames.has(`${name}-${n}`)) n++\n name = `${name}-${n}`\n }\n usedNames.set(name, signature)\n\n combo = { className: name, classes: ordered }\n bySignature.set(signature, combo)\n }\n\n // Replace the gradient utilities with the single generated class.\n const rest = tokens.filter(t => !gradientClasses.includes(t))\n el.attribs.class = [...rest, combo.className].join(' ')\n })\n\n return [...bySignature.values()]\n}\n\n/**\n * Append ` source(none)` to Tailwind imports so auto source detection\n * is off and only our explicit `@source` directives apply. Skips\n * imports that already carry a `source(...)` modifier.\n */\nexport function rewriteImportsSourceNone(css: string): string {\n return css.replace(\n /(@import\\s+[\"'](?:@maizzle\\/)?tailwindcss(?:\\/[^\"']*)?[\"'][^;]*);/g,\n (match, stmt) => stmt.includes('source(') ? match : `${stmt} source(none);`,\n )\n}\n\n/**\n * Tailwind CSS transformer.\n *\n * Compiles CSS inside <style> tags in the DOM using\n * @tailwindcss/postcss, then lowers modern CSS syntax with lightningcss.\n *\n * Configures Tailwind sources to scan:\n * - Rendered class attributes (via `@source inline`) for all classes from all components\n * - The template's module import closure (via `@source` directives), or user\n * project files (via Tailwind's auto-detection) when `css.scopedSources` is off\n *\n * User `@source` and `@source not directives` in style tags are preserved.\n * Source directives are only added to style tags that import Tailwind.\n *\n * Runs as the first transformer in the pipeline so that subsequent\n * transformers (inliner, purge, etc.) work with fully compiled CSS.\n */\nexport async function tailwindcss(dom: ChildNode[], config: MaizzleConfig, filePath?: string, sourceFiles?: string[]): Promise<ChildNode[]> {\n const styleTags: { node: Element; cssContent: string }[] = []\n\n walk(dom, (node) => {\n if ((node as Element).name !== 'style') return\n\n const el = node as Element\n const attrs = el.attribs\n\n /**\n * `raw` opts out of compilation entirely (marker is consumed here).\n * `embed`/`data-embed` only signal \"preserve tag after inlining\"\n * — they still need to go through compile so Tailwind/@apply\n * resolves.\n */\n if ('raw' in attrs) {\n delete el.attribs.raw\n return\n }\n\n // Get text content from children and decode HTML entities\n const rawContent = el.children\n .filter(child => child.type === 'text')\n .map(child => (child as any).data)\n .join('')\n\n if (!rawContent.trim()) return\n\n styleTags.push({ node: el, cssContent: decodeStyleEntities(rawContent) })\n })\n\n if (!styleTags.length) return dom\n\n const fromPath = filePath ?? resolve(process.cwd(), 'template.vue')\n const fromDir = dirname(fromPath)\n\n // Only compute source directives if at least one style tag uses Tailwind\n const hasTailwindStyles = styleTags.some(({ cssContent }) => usesTailwind(cssContent))\n const sourceDirectives = hasTailwindStyles\n ? buildSourceDirectives(dom, config, fromDir, sourceFiles)\n : ''\n\n const scoped = config.css?.scopedSources !== false && !!sourceFiles?.length\n\n /**\n * Collect gradient combos and rewrite elements to single classes.\n * Runs after source directives are built (so the utility classes are\n * still scanned) and only feeds the first Tailwind style tag, whose\n * `:root` holds the theme colors the flat rules reference.\n */\n const gradientCombos = hasTailwindStyles ? collectGradientCombos(dom) : []\n const firstTailwindStyle = styleTags.findIndex(({ cssContent }) => usesTailwind(cssContent))\n\n for (let i = 0; i < styleTags.length; i++) {\n const { node, cssContent } = styleTags[i]\n\n /**\n * Only add source directives to style tags that import Tailwind —\n * plain CSS doesn't need them and @tailwindcss/postcss would\n * leave the directives unconsumed in the output.\n */\n const usesTw = usesTailwind(cssContent)\n const fullCss = usesTw\n ? `${scoped ? rewriteImportsSourceNone(cssContent) : cssContent}\\n${sourceDirectives}`\n : cssContent\n\n const combos = i === firstTailwindStyle ? gradientCombos : []\n\n try {\n const optimized = await compileTailwindCss(fullCss, config, `${fromPath}?style=${i}`, combos)\n\n // Replace the style tag's children with the compiled CSS\n node.children = [{\n type: 'text',\n data: optimized,\n parent: node,\n } as any]\n } catch {\n /**\n * If CSS processing fails, still replace with decoded content\n * so HTML entities don't break the CSS.\n */\n node.children = [{\n type: 'text',\n data: cssContent,\n parent: node,\n } as any]\n }\n }\n\n return dom\n}\n"],"mappings":"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"}
@@ -62,7 +62,8 @@ function urlQueryDom(dom, params = {}, options = {}) {
62
62
  encode: false,
63
63
  ...options.qs ?? {}
64
64
  };
65
- const elements = selectAll(tags.join(", "), dom);
65
+ const selector = tags.join(", ");
66
+ const elements = selectAll(selector, dom);
66
67
  for (const el of elements) for (const attr of attributes) {
67
68
  const value = el.attribs[attr];
68
69
  if (!value) continue;