@maizzle/framework 6.0.0-rc.24 → 6.0.0-rc.26

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 (143) hide show
  1. package/dist/build.d.ts +19 -1
  2. package/dist/build.d.ts.map +1 -1
  3. package/dist/build.js +139 -102
  4. package/dist/build.js.map +1 -1
  5. package/dist/components/Body.vue +12 -0
  6. package/dist/components/Button.vue +16 -29
  7. package/dist/components/CodeBlock.vue +5 -4
  8. package/dist/components/CodeInline.vue +9 -8
  9. package/dist/components/Column.vue +17 -4
  10. package/dist/components/Container.vue +7 -11
  11. package/dist/components/Hr.vue +1 -1
  12. package/dist/components/Img.vue +39 -22
  13. package/dist/components/Layout.vue +1 -1
  14. package/dist/components/Markdown.vue +9 -14
  15. package/dist/components/QrCode.vue +2 -2
  16. package/dist/components/Section.vue +9 -6
  17. package/dist/components/Text.vue +2 -2
  18. package/dist/components/utils.d.ts +25 -1
  19. package/dist/components/utils.d.ts.map +1 -1
  20. package/dist/components/utils.js +29 -1
  21. package/dist/components/utils.js.map +1 -1
  22. package/dist/components/utils.ts +46 -0
  23. package/dist/composables/useConfig.d.ts.map +1 -1
  24. package/dist/composables/useCurrentTemplate.d.ts.map +1 -1
  25. package/dist/composables/useEvent.d.ts.map +1 -1
  26. package/dist/composables/useFont.js.map +1 -1
  27. package/dist/config/index.js +1 -1
  28. package/dist/config/index.js.map +1 -1
  29. package/dist/events/index.d.ts.map +1 -1
  30. package/dist/events/index.js.map +1 -1
  31. package/dist/index.js +2 -2
  32. package/dist/plaintext.js.map +1 -1
  33. package/dist/plugins/postcss/mergeMediaQueries.js.map +1 -1
  34. package/dist/plugins/postcss/pruneVars.js.map +1 -1
  35. package/dist/plugins/postcss/quoteFontFamilies.d.ts.map +1 -1
  36. package/dist/plugins/postcss/quoteFontFamilies.js.map +1 -1
  37. package/dist/plugins/postcss/removeDeclarations.js.map +1 -1
  38. package/dist/plugins/postcss/resolveProps.d.ts.map +1 -1
  39. package/dist/plugins/postcss/resolveProps.js +0 -3
  40. package/dist/plugins/postcss/resolveProps.js.map +1 -1
  41. package/dist/plugins/postcss/tailwindCleanup.js.map +1 -1
  42. package/dist/prepare.js +1 -1
  43. package/dist/prepare.js.map +1 -1
  44. package/dist/render/active.d.ts.map +1 -1
  45. package/dist/render/buildTemplate.d.ts +49 -0
  46. package/dist/render/buildTemplate.d.ts.map +1 -0
  47. package/dist/render/buildTemplate.js +139 -0
  48. package/dist/render/buildTemplate.js.map +1 -0
  49. package/dist/render/createRenderer.d.ts +3 -1
  50. package/dist/render/createRenderer.d.ts.map +1 -1
  51. package/dist/render/createRenderer.js +43 -10
  52. package/dist/render/createRenderer.js.map +1 -1
  53. package/dist/render/index.js +1 -1
  54. package/dist/render/index.js.map +1 -1
  55. package/dist/render/injectFonts.js.map +1 -1
  56. package/dist/render/parallel/buildWorker.d.ts +31 -0
  57. package/dist/render/parallel/buildWorker.d.ts.map +1 -0
  58. package/dist/render/parallel/buildWorker.js +66 -0
  59. package/dist/render/parallel/buildWorker.js.map +1 -0
  60. package/dist/render/parallel/worker.mjs +28 -0
  61. package/dist/render/plugins/codeBlockExtract.d.ts.map +1 -1
  62. package/dist/render/plugins/codeBlockExtract.js.map +1 -1
  63. package/dist/render/plugins/markdownExtract.d.ts.map +1 -1
  64. package/dist/render/plugins/markdownExtract.js.map +1 -1
  65. package/dist/render/plugins/rawExtract.d.ts.map +1 -1
  66. package/dist/render/plugins/rawExtract.js.map +1 -1
  67. package/dist/render/plugins/rowSourceLocation.d.ts.map +1 -1
  68. package/dist/render/plugins/rowSourceLocation.js.map +1 -1
  69. package/dist/serve.d.ts.map +1 -1
  70. package/dist/serve.js +73 -53
  71. package/dist/serve.js.map +1 -1
  72. package/dist/server/compatibility.d.ts.map +1 -1
  73. package/dist/server/compatibility.js.map +1 -1
  74. package/dist/server/linter.js.map +1 -1
  75. package/dist/server/sfc-utils.js +1 -1
  76. package/dist/server/sfc-utils.js.map +1 -1
  77. package/dist/server/ui/pages/Preview.vue +34 -11
  78. package/dist/server/ui/vite-env.d.ts +1 -0
  79. package/dist/tests/render/_helpers.d.ts.map +1 -1
  80. package/dist/tests/render/_helpers.js.map +1 -1
  81. package/dist/transformers/addAttributes.js +2 -3
  82. package/dist/transformers/addAttributes.js.map +1 -1
  83. package/dist/transformers/base.d.ts +1 -1
  84. package/dist/transformers/base.d.ts.map +1 -1
  85. package/dist/transformers/base.js +5 -10
  86. package/dist/transformers/base.js.map +1 -1
  87. package/dist/transformers/columnWidth.d.ts.map +1 -1
  88. package/dist/transformers/columnWidth.js +2 -7
  89. package/dist/transformers/columnWidth.js.map +1 -1
  90. package/dist/transformers/entities.js.map +1 -1
  91. package/dist/transformers/filters/defaults.js.map +1 -1
  92. package/dist/transformers/filters/index.js.map +1 -1
  93. package/dist/transformers/format.js.map +1 -1
  94. package/dist/transformers/imgWidth.d.ts +20 -0
  95. package/dist/transformers/imgWidth.d.ts.map +1 -0
  96. package/dist/transformers/imgWidth.js +76 -0
  97. package/dist/transformers/imgWidth.js.map +1 -0
  98. package/dist/transformers/index.d.ts.map +1 -1
  99. package/dist/transformers/index.js +2 -0
  100. package/dist/transformers/index.js.map +1 -1
  101. package/dist/transformers/inlineCss.d.ts +3 -2
  102. package/dist/transformers/inlineCss.d.ts.map +1 -1
  103. package/dist/transformers/inlineCss.js.map +1 -1
  104. package/dist/transformers/inlineLink.js +1 -1
  105. package/dist/transformers/inlineLink.js.map +1 -1
  106. package/dist/transformers/minify.js.map +1 -1
  107. package/dist/transformers/minifyCodeInline.js.map +1 -1
  108. package/dist/transformers/msoPlaceholders.d.ts.map +1 -1
  109. package/dist/transformers/msoPlaceholders.js +2 -7
  110. package/dist/transformers/msoPlaceholders.js.map +1 -1
  111. package/dist/transformers/purgeCss.js.map +1 -1
  112. package/dist/transformers/replaceStrings.js.map +1 -1
  113. package/dist/transformers/safeSelectors.js.map +1 -1
  114. package/dist/transformers/shorthandCss.js.map +1 -1
  115. package/dist/transformers/tailwindComponent.js.map +1 -1
  116. package/dist/transformers/tailwindcss.js +1 -1
  117. package/dist/transformers/tailwindcss.js.map +1 -1
  118. package/dist/transformers/urlQuery.js.map +1 -1
  119. package/dist/types/config.d.ts +26 -4
  120. package/dist/types/config.d.ts.map +1 -1
  121. package/dist/utils/ast/serializer.js.map +1 -1
  122. package/dist/utils/compileTailwindCss.js.map +1 -1
  123. package/dist/utils/componentSources.js.map +1 -1
  124. package/dist/utils/cssBox.d.ts.map +1 -1
  125. package/dist/utils/cssBox.js +2 -7
  126. package/dist/utils/cssBox.js.map +1 -1
  127. package/dist/utils/decodeStyleEntities.js.map +1 -1
  128. package/dist/utils/url.js.map +1 -1
  129. package/dist/utils/watchPaths.js.map +1 -1
  130. package/node_modules/@clack/core/CHANGELOG.md +30 -0
  131. package/node_modules/@clack/core/dist/index.d.mts +109 -3
  132. package/node_modules/@clack/core/dist/index.mjs +972 -17
  133. package/node_modules/@clack/core/package.json +2 -1
  134. package/node_modules/@clack/prompts/CHANGELOG.md +42 -0
  135. package/node_modules/@clack/prompts/README.md +30 -9
  136. package/node_modules/@clack/prompts/dist/index.d.mts +458 -27
  137. package/node_modules/@clack/prompts/dist/index.mjs +1378 -141
  138. package/node_modules/@clack/prompts/package.json +2 -2
  139. package/node_modules/tinyexec/package.json +4 -4
  140. package/package.json +13 -11
  141. package/dist/components/Overlap.vue +0 -156
  142. package/node_modules/@clack/core/dist/index.mjs.map +0 -1
  143. package/node_modules/@clack/prompts/dist/index.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"pruneVars.js","names":[],"sources":["../../../src/plugins/postcss/pruneVars.ts"],"sourcesContent":["/**\n * postcss-prune-var\n * Removes unused CSS custom properties.\n */\n\nimport type { Declaration, Plugin, Root } from 'postcss';\n\nconst PLUGIN_NAME = 'postcss-prune-var';\n\nconst VAR_RE = /var\\(\\s*(--[^ ,);]+)/g;\n\ninterface VarRecord {\n uses: number;\n declarations: Set<Declaration>;\n dependencies: Set<string>;\n}\n\nexport default (): Plugin => {\n return {\n postcssPlugin: PLUGIN_NAME,\n\n Once(root: Root) {\n const records = new Map<string, VarRecord>();\n const usedVars = new Set<string>();\n\n const getRecord = (name: string): VarRecord => {\n let r = records.get(name);\n if (!r) {\n r = { uses: 0, declarations: new Set(), dependencies: new Set() };\n records.set(name, r);\n }\n return r;\n };\n\n const registerUse = (name: string, seen = new Set<string>()) => {\n const r = getRecord(name);\n r.uses++;\n seen.add(name);\n for (const dep of r.dependencies) {\n if (!seen.has(dep)) registerUse(dep, seen);\n }\n };\n\n // Build dependency graph\n root.walkDecls((decl) => {\n const isVar = decl.prop.startsWith('--');\n\n if (isVar) getRecord(decl.prop).declarations.add(decl);\n\n if (!decl.value.includes('var(')) return;\n\n let m;\n VAR_RE.lastIndex = 0;\n while ((m = VAR_RE.exec(decl.value))) {\n const ref = m[1].trim();\n if (isVar) {\n getRecord(decl.prop).dependencies.add(ref);\n } else {\n usedVars.add(ref);\n }\n }\n });\n\n // Propagate usage through the graph\n for (const v of usedVars) registerUse(v);\n\n // Remove declarations with zero uses\n for (const { uses, declarations } of records.values()) {\n if (uses === 0) {\n for (const decl of declarations) decl.remove();\n }\n }\n },\n };\n};\n\nexport const postcss = true;\n"],"mappings":";AAOA,MAAM,cAAc;AAEpB,MAAM,SAAS;AAQf,IAAA,0BAA6B;CAC3B,OAAO;EACL,eAAe;EAEf,KAAK,MAAY;GACf,MAAM,0BAAU,IAAI,IAAuB;GAC3C,MAAM,2BAAW,IAAI,IAAY;GAEjC,MAAM,aAAa,SAA4B;IAC7C,IAAI,IAAI,QAAQ,IAAI,IAAI;IACxB,IAAI,CAAC,GAAG;KACN,IAAI;MAAE,MAAM;MAAG,8BAAc,IAAI,IAAI;MAAG,8BAAc,IAAI,IAAI;KAAE;KAChE,QAAQ,IAAI,MAAM,CAAC;IACrB;IACA,OAAO;GACT;GAEA,MAAM,eAAe,MAAc,uBAAO,IAAI,IAAY,MAAM;IAC9D,MAAM,IAAI,UAAU,IAAI;IACxB,EAAE;IACF,KAAK,IAAI,IAAI;IACb,KAAK,MAAM,OAAO,EAAE,cAClB,IAAI,CAAC,KAAK,IAAI,GAAG,GAAG,YAAY,KAAK,IAAI;GAE7C;GAGA,KAAK,WAAW,SAAS;IACvB,MAAM,QAAQ,KAAK,KAAK,WAAW,IAAI;IAEvC,IAAI,OAAO,UAAU,KAAK,IAAI,EAAE,aAAa,IAAI,IAAI;IAErD,IAAI,CAAC,KAAK,MAAM,SAAS,MAAM,GAAG;IAElC,IAAI;IACJ,OAAO,YAAY;IACnB,OAAQ,IAAI,OAAO,KAAK,KAAK,KAAK,GAAI;KACpC,MAAM,MAAM,EAAE,GAAG,KAAK;KACtB,IAAI,OACF,UAAU,KAAK,IAAI,EAAE,aAAa,IAAI,GAAG;UAEzC,SAAS,IAAI,GAAG;IAEpB;GACF,CAAC;GAGD,KAAK,MAAM,KAAK,UAAU,YAAY,CAAC;GAGvC,KAAK,MAAM,EAAE,MAAM,kBAAkB,QAAQ,OAAO,GAClD,IAAI,SAAS,GACX,KAAK,MAAM,QAAQ,cAAc,KAAK,OAAO;EAGnD;CACF;AACF;AAEA,MAAa,UAAU"}
1
+ {"version":3,"file":"pruneVars.js","names":[],"sources":["../../../src/plugins/postcss/pruneVars.ts"],"sourcesContent":["/**\n * postcss-prune-var\n * Removes unused CSS custom properties.\n */\n\nimport type { Declaration, Plugin, Root } from 'postcss';\n\nconst PLUGIN_NAME = 'postcss-prune-var';\n\nconst VAR_RE = /var\\(\\s*(--[^ ,);]+)/g;\n\ninterface VarRecord {\n uses: number;\n declarations: Set<Declaration>;\n dependencies: Set<string>;\n}\n\nexport default (): Plugin => {\n return {\n postcssPlugin: PLUGIN_NAME,\n\n Once(root: Root) {\n const records = new Map<string, VarRecord>();\n const usedVars = new Set<string>();\n\n const getRecord = (name: string): VarRecord => {\n let r = records.get(name);\n if (!r) {\n r = { uses: 0, declarations: new Set(), dependencies: new Set() };\n records.set(name, r);\n }\n return r;\n };\n\n const registerUse = (name: string, seen = new Set<string>()) => {\n const r = getRecord(name);\n r.uses++;\n seen.add(name);\n for (const dep of r.dependencies) {\n if (!seen.has(dep)) registerUse(dep, seen);\n }\n };\n\n // Build dependency graph\n root.walkDecls((decl) => {\n const isVar = decl.prop.startsWith('--');\n\n if (isVar) getRecord(decl.prop).declarations.add(decl);\n\n if (!decl.value.includes('var(')) return;\n\n let m;\n VAR_RE.lastIndex = 0;\n while ((m = VAR_RE.exec(decl.value))) {\n const ref = m[1].trim();\n if (isVar) {\n getRecord(decl.prop).dependencies.add(ref);\n } else {\n usedVars.add(ref);\n }\n }\n });\n\n // Propagate usage through the graph\n for (const v of usedVars) registerUse(v);\n\n // Remove declarations with zero uses\n for (const { uses, declarations } of records.values()) {\n if (uses === 0) {\n for (const decl of declarations) decl.remove();\n }\n }\n },\n };\n};\n\nexport const postcss = true;\n"],"mappings":";AAOA,MAAM,cAAc;AAEpB,MAAM,SAAS;AAQf,IAAA,0BAA6B;CAC3B,OAAO;EACL,eAAe;EAEf,KAAK,MAAY;GACf,MAAM,0BAAU,IAAI,IAAuB;GAC3C,MAAM,2BAAW,IAAI,IAAY;GAEjC,MAAM,aAAa,SAA4B;IAC7C,IAAI,IAAI,QAAQ,IAAI,IAAI;IACxB,IAAI,CAAC,GAAG;KACN,IAAI;MAAE,MAAM;MAAG,8BAAc,IAAI,IAAI;MAAG,8BAAc,IAAI,IAAI;KAAE;KAChE,QAAQ,IAAI,MAAM,CAAC;IACrB;IACA,OAAO;GACT;GAEA,MAAM,eAAe,MAAc,uBAAO,IAAI,IAAY,MAAM;IAC9D,MAAM,IAAI,UAAU,IAAI;IACxB,EAAE;IACF,KAAK,IAAI,IAAI;IACb,KAAK,MAAM,OAAO,EAAE,cAClB,IAAI,CAAC,KAAK,IAAI,GAAG,GAAG,YAAY,KAAK,IAAI;GAE7C;GAGA,KAAK,WAAW,SAAS;IACvB,MAAM,QAAQ,KAAK,KAAK,WAAW,IAAI;IAEvC,IAAI,OAAO,UAAU,KAAK,IAAI,CAAC,CAAC,aAAa,IAAI,IAAI;IAErD,IAAI,CAAC,KAAK,MAAM,SAAS,MAAM,GAAG;IAElC,IAAI;IACJ,OAAO,YAAY;IACnB,OAAQ,IAAI,OAAO,KAAK,KAAK,KAAK,GAAI;KACpC,MAAM,MAAM,EAAE,EAAE,CAAC,KAAK;KACtB,IAAI,OACF,UAAU,KAAK,IAAI,CAAC,CAAC,aAAa,IAAI,GAAG;UAEzC,SAAS,IAAI,GAAG;IAEpB;GACF,CAAC;GAGD,KAAK,MAAM,KAAK,UAAU,YAAY,CAAC;GAGvC,KAAK,MAAM,EAAE,MAAM,kBAAkB,QAAQ,OAAO,GAClD,IAAI,SAAS,GACX,KAAK,MAAM,QAAQ,cAAc,KAAK,OAAO;EAGnD;CACF;AACF;AAEA,MAAa,UAAU"}
@@ -1 +1 @@
1
- {"version":3,"file":"quoteFontFamilies.d.ts","names":[],"sources":["../../../src/plugins/postcss/quoteFontFamilies.ts"],"mappings":";;;;;AA+CA;;;iBAAgB,iBAAA,CAAA,GAAqB,MAAM;AAAA,cA6B9B,OAAA"}
1
+ {"version":3,"file":"quoteFontFamilies.d.ts","names":[],"sources":["../../../src/plugins/postcss/quoteFontFamilies.ts"],"mappings":";;;;;AA+CA;;;iBAAgB,iBAAA,IAAqB,MAAM;AAAA,cA6B9B,OAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"quoteFontFamilies.js","names":[],"sources":["../../../src/plugins/postcss/quoteFontFamilies.ts"],"sourcesContent":["import type { Plugin } from 'postcss'\n\nconst GENERIC_KEYWORDS = new Set([\n 'serif', 'sans-serif', 'monospace', 'cursive', 'fantasy',\n 'system-ui', 'ui-serif', 'ui-sans-serif', 'ui-monospace', 'ui-rounded',\n 'emoji', 'math', 'fangsong',\n 'inherit', 'initial', 'unset', 'revert', 'revert-layer',\n])\n\n/**\n * Split a font-family value on top-level commas only, preserving quoted\n * strings and parenthesised groups like `var(...)`.\n */\nfunction splitFamilies(value: string): string[] {\n const parts: string[] = []\n let depth = 0\n let quote: string | null = null\n let start = 0\n\n for (let i = 0; i < value.length; i++) {\n const ch = value[i]\n if (quote) {\n if (ch === '\\\\') { i++; continue }\n if (ch === quote) quote = null\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n continue\n }\n if (ch === '(') depth++\n else if (ch === ')') depth--\n else if (ch === ',' && depth === 0) {\n parts.push(value.slice(start, i).trim())\n start = i + 1\n }\n }\n\n parts.push(value.slice(start).trim())\n return parts.filter(Boolean)\n}\n\n/**\n * Re-quote multi-word font-family identifiers that lightningcss \"optimised\"\n * by removing quotes. CSS allows space-separated identifiers as a family\n * name, but Google Fonts (and most style guides) prescribe quoted form.\n */\nexport function quoteFontFamilies(): Plugin {\n return {\n postcssPlugin: 'quote-font-families',\n Declaration: {\n 'font-family': (decl) => {\n const value = decl.value\n if (!value || !/\\s/.test(value)) return\n\n const families = splitFamilies(value)\n let changed = false\n\n const fixed = families.map((token) => {\n if (token.startsWith('\"') || token.startsWith(\"'\")) return token\n if (token.startsWith('var(')) return token\n if (!token.includes(' ')) return token\n if (GENERIC_KEYWORDS.has(token.toLowerCase())) return token\n\n changed = true\n return `\"${token}\"`\n })\n\n if (changed) {\n decl.value = fixed.join(', ')\n }\n },\n },\n }\n}\n\nexport const postcss = true\n"],"mappings":";AAEA,MAAM,mBAAmB,IAAI,IAAI;CAC/B;CAAS;CAAc;CAAa;CAAW;CAC/C;CAAa;CAAY;CAAiB;CAAgB;CAC1D;CAAS;CAAQ;CACjB;CAAW;CAAW;CAAS;CAAU;AAC3C,CAAC;;;;;AAMD,SAAS,cAAc,OAAyB;CAC9C,MAAM,QAAkB,CAAC;CACzB,IAAI,QAAQ;CACZ,IAAI,QAAuB;CAC3B,IAAI,QAAQ;CAEZ,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,KAAK,MAAM;EACjB,IAAI,OAAO;GACT,IAAI,OAAO,MAAM;IAAE;IAAK;GAAS;GACjC,IAAI,OAAO,OAAO,QAAQ;GAC1B;EACF;EACA,IAAI,OAAO,QAAO,OAAO,KAAK;GAC5B,QAAQ;GACR;EACF;EACA,IAAI,OAAO,KAAK;OACX,IAAI,OAAO,KAAK;OAChB,IAAI,OAAO,OAAO,UAAU,GAAG;GAClC,MAAM,KAAK,MAAM,MAAM,OAAO,CAAC,EAAE,KAAK,CAAC;GACvC,QAAQ,IAAI;EACd;CACF;CAEA,MAAM,KAAK,MAAM,MAAM,KAAK,EAAE,KAAK,CAAC;CACpC,OAAO,MAAM,OAAO,OAAO;AAC7B;;;;;;AAOA,SAAgB,oBAA4B;CAC1C,OAAO;EACL,eAAe;EACf,aAAa,EACX,gBAAgB,SAAS;GACvB,MAAM,QAAQ,KAAK;GACnB,IAAI,CAAC,SAAS,CAAC,KAAK,KAAK,KAAK,GAAG;GAEjC,MAAM,WAAW,cAAc,KAAK;GACpC,IAAI,UAAU;GAEd,MAAM,QAAQ,SAAS,KAAK,UAAU;IACpC,IAAI,MAAM,WAAW,IAAG,KAAK,MAAM,WAAW,GAAG,GAAG,OAAO;IAC3D,IAAI,MAAM,WAAW,MAAM,GAAG,OAAO;IACrC,IAAI,CAAC,MAAM,SAAS,GAAG,GAAG,OAAO;IACjC,IAAI,iBAAiB,IAAI,MAAM,YAAY,CAAC,GAAG,OAAO;IAEtD,UAAU;IACV,OAAO,IAAI,MAAM;GACnB,CAAC;GAED,IAAI,SACF,KAAK,QAAQ,MAAM,KAAK,IAAI;EAEhC,EACF;CACF;AACF;AAEA,MAAa,UAAU"}
1
+ {"version":3,"file":"quoteFontFamilies.js","names":[],"sources":["../../../src/plugins/postcss/quoteFontFamilies.ts"],"sourcesContent":["import type { Plugin } from 'postcss'\n\nconst GENERIC_KEYWORDS = new Set([\n 'serif', 'sans-serif', 'monospace', 'cursive', 'fantasy',\n 'system-ui', 'ui-serif', 'ui-sans-serif', 'ui-monospace', 'ui-rounded',\n 'emoji', 'math', 'fangsong',\n 'inherit', 'initial', 'unset', 'revert', 'revert-layer',\n])\n\n/**\n * Split a font-family value on top-level commas only, preserving quoted\n * strings and parenthesised groups like `var(...)`.\n */\nfunction splitFamilies(value: string): string[] {\n const parts: string[] = []\n let depth = 0\n let quote: string | null = null\n let start = 0\n\n for (let i = 0; i < value.length; i++) {\n const ch = value[i]\n if (quote) {\n if (ch === '\\\\') { i++; continue }\n if (ch === quote) quote = null\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n continue\n }\n if (ch === '(') depth++\n else if (ch === ')') depth--\n else if (ch === ',' && depth === 0) {\n parts.push(value.slice(start, i).trim())\n start = i + 1\n }\n }\n\n parts.push(value.slice(start).trim())\n return parts.filter(Boolean)\n}\n\n/**\n * Re-quote multi-word font-family identifiers that lightningcss \"optimised\"\n * by removing quotes. CSS allows space-separated identifiers as a family\n * name, but Google Fonts (and most style guides) prescribe quoted form.\n */\nexport function quoteFontFamilies(): Plugin {\n return {\n postcssPlugin: 'quote-font-families',\n Declaration: {\n 'font-family': (decl) => {\n const value = decl.value\n if (!value || !/\\s/.test(value)) return\n\n const families = splitFamilies(value)\n let changed = false\n\n const fixed = families.map((token) => {\n if (token.startsWith('\"') || token.startsWith(\"'\")) return token\n if (token.startsWith('var(')) return token\n if (!token.includes(' ')) return token\n if (GENERIC_KEYWORDS.has(token.toLowerCase())) return token\n\n changed = true\n return `\"${token}\"`\n })\n\n if (changed) {\n decl.value = fixed.join(', ')\n }\n },\n },\n }\n}\n\nexport const postcss = true\n"],"mappings":";AAEA,MAAM,mBAAmB,IAAI,IAAI;CAC/B;CAAS;CAAc;CAAa;CAAW;CAC/C;CAAa;CAAY;CAAiB;CAAgB;CAC1D;CAAS;CAAQ;CACjB;CAAW;CAAW;CAAS;CAAU;AAC3C,CAAC;;;;;AAMD,SAAS,cAAc,OAAyB;CAC9C,MAAM,QAAkB,CAAC;CACzB,IAAI,QAAQ;CACZ,IAAI,QAAuB;CAC3B,IAAI,QAAQ;CAEZ,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,KAAK,MAAM;EACjB,IAAI,OAAO;GACT,IAAI,OAAO,MAAM;IAAE;IAAK;GAAS;GACjC,IAAI,OAAO,OAAO,QAAQ;GAC1B;EACF;EACA,IAAI,OAAO,QAAO,OAAO,KAAK;GAC5B,QAAQ;GACR;EACF;EACA,IAAI,OAAO,KAAK;OACX,IAAI,OAAO,KAAK;OAChB,IAAI,OAAO,OAAO,UAAU,GAAG;GAClC,MAAM,KAAK,MAAM,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;GACvC,QAAQ,IAAI;EACd;CACF;CAEA,MAAM,KAAK,MAAM,MAAM,KAAK,CAAC,CAAC,KAAK,CAAC;CACpC,OAAO,MAAM,OAAO,OAAO;AAC7B;;;;;;AAOA,SAAgB,oBAA4B;CAC1C,OAAO;EACL,eAAe;EACf,aAAa,EACX,gBAAgB,SAAS;GACvB,MAAM,QAAQ,KAAK;GACnB,IAAI,CAAC,SAAS,CAAC,KAAK,KAAK,KAAK,GAAG;GAEjC,MAAM,WAAW,cAAc,KAAK;GACpC,IAAI,UAAU;GAEd,MAAM,QAAQ,SAAS,KAAK,UAAU;IACpC,IAAI,MAAM,WAAW,IAAG,KAAK,MAAM,WAAW,GAAG,GAAG,OAAO;IAC3D,IAAI,MAAM,WAAW,MAAM,GAAG,OAAO;IACrC,IAAI,CAAC,MAAM,SAAS,GAAG,GAAG,OAAO;IACjC,IAAI,iBAAiB,IAAI,MAAM,YAAY,CAAC,GAAG,OAAO;IAEtD,UAAU;IACV,OAAO,IAAI,MAAM;GACnB,CAAC;GAED,IAAI,SACF,KAAK,QAAQ,MAAM,KAAK,IAAI;EAEhC,EACF;CACF;AACF;AAEA,MAAa,UAAU"}
@@ -1 +1 @@
1
- {"version":3,"file":"removeDeclarations.js","names":[],"sources":["../../../src/plugins/postcss/removeDeclarations.ts"],"sourcesContent":["/**\n * postcss-remove-declarations\n *\n * Removes CSS declarations (or whole rules) based on a selector map.\n *\n * The `remove` option maps a selector string to one of:\n *\n * - `\"*\"` — remove the entire rule\n * - `string` — remove the single named property\n * - `string[]` — remove every listed property\n * - `Record<string, string>` — remove a property only when its value matches.\n * Append `!important` to the value to restrict the match\n * to non-important declarations only.\n *\n * Example:\n * removeDeclarations({\n * remove: {\n * ':root': '*',\n * '.foo': ['color', 'margin'],\n * '.bar': { color: 'red' },\n * }\n * })\n */\n\nimport type { Plugin, Root } from 'postcss'\n\nexport type RemoveValue =\n | '*'\n | string\n | string[]\n | Record<string, string>\n\nexport interface RemoveDeclarationsOptions {\n remove: Record<string, RemoveValue>\n}\n\nconst IMPORTANT = '!important'\n\nfunction normalizeSelector(selector: string): string {\n return selector.replace(/(\\r\\n|\\n|\\r)/gm, '')\n}\n\nexport default (options: RemoveDeclarationsOptions): Plugin => {\n return {\n postcssPlugin: 'postcss-remove-declarations',\n\n Once(root: Root) {\n const remove = options.remove ?? {}\n\n root.walkRules((rule) => {\n let toRemove = remove[normalizeSelector(rule.selector)]\n\n if (!toRemove) return\n\n // Remove the entire rule\n if (toRemove === '*') {\n rule.remove()\n return\n }\n\n // Normalise a bare string into an array\n if (typeof toRemove === 'string') {\n toRemove = [toRemove]\n }\n\n if (Array.isArray(toRemove)) {\n const props = toRemove as string[]\n rule.walkDecls((decl) => {\n if (props.includes(decl.prop)) decl.remove()\n })\n } else if (typeof toRemove === 'object') {\n // Object: match both property and value\n const map = toRemove as Record<string, string>\n rule.walkDecls((decl) => {\n if (!(decl.prop in map)) return\n\n let expected = map[decl.prop]\n const requireNonImportant = expected.endsWith(IMPORTANT)\n\n if (requireNonImportant) {\n expected = expected.slice(0, -IMPORTANT.length).trim()\n }\n\n if (decl.value !== expected) return\n if (decl.important && requireNonImportant) return\n\n decl.remove()\n })\n }\n\n // Remove the rule if all declarations were removed\n if (rule.nodes?.length === 0) {\n rule.remove()\n }\n })\n },\n }\n}\n\nexport const postcss = true\n"],"mappings":";AAoCA,MAAM,YAAY;AAElB,SAAS,kBAAkB,UAA0B;CACnD,OAAO,SAAS,QAAQ,kBAAkB,EAAE;AAC9C;AAEA,IAAA,8BAAgB,YAA+C;CAC7D,OAAO;EACL,eAAe;EAEf,KAAK,MAAY;GACf,MAAM,SAAS,QAAQ,UAAU,CAAC;GAElC,KAAK,WAAW,SAAS;IACvB,IAAI,WAAW,OAAO,kBAAkB,KAAK,QAAQ;IAErD,IAAI,CAAC,UAAU;IAGf,IAAI,aAAa,KAAK;KACpB,KAAK,OAAO;KACZ;IACF;IAGA,IAAI,OAAO,aAAa,UACtB,WAAW,CAAC,QAAQ;IAGtB,IAAI,MAAM,QAAQ,QAAQ,GAAG;KAC3B,MAAM,QAAQ;KACd,KAAK,WAAW,SAAS;MACvB,IAAI,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,OAAO;KAC7C,CAAC;IACH,OAAO,IAAI,OAAO,aAAa,UAAU;KAEvC,MAAM,MAAM;KACZ,KAAK,WAAW,SAAS;MACvB,IAAI,EAAE,KAAK,QAAQ,MAAM;MAEzB,IAAI,WAAW,IAAI,KAAK;MACxB,MAAM,sBAAsB,SAAS,SAAS,SAAS;MAEvD,IAAI,qBACF,WAAW,SAAS,MAAM,GAAG,GAAiB,EAAE,KAAK;MAGvD,IAAI,KAAK,UAAU,UAAU;MAC7B,IAAI,KAAK,aAAa,qBAAqB;MAE3C,KAAK,OAAO;KACd,CAAC;IACH;IAGA,IAAI,KAAK,OAAO,WAAW,GACzB,KAAK,OAAO;GAEhB,CAAC;EACH;CACF;AACF;AAEA,MAAa,UAAU"}
1
+ {"version":3,"file":"removeDeclarations.js","names":[],"sources":["../../../src/plugins/postcss/removeDeclarations.ts"],"sourcesContent":["/**\n * postcss-remove-declarations\n *\n * Removes CSS declarations (or whole rules) based on a selector map.\n *\n * The `remove` option maps a selector string to one of:\n *\n * - `\"*\"` — remove the entire rule\n * - `string` — remove the single named property\n * - `string[]` — remove every listed property\n * - `Record<string, string>` — remove a property only when its value matches.\n * Append `!important` to the value to restrict the match\n * to non-important declarations only.\n *\n * Example:\n * removeDeclarations({\n * remove: {\n * ':root': '*',\n * '.foo': ['color', 'margin'],\n * '.bar': { color: 'red' },\n * }\n * })\n */\n\nimport type { Plugin, Root } from 'postcss'\n\nexport type RemoveValue =\n | '*'\n | string\n | string[]\n | Record<string, string>\n\nexport interface RemoveDeclarationsOptions {\n remove: Record<string, RemoveValue>\n}\n\nconst IMPORTANT = '!important'\n\nfunction normalizeSelector(selector: string): string {\n return selector.replace(/(\\r\\n|\\n|\\r)/gm, '')\n}\n\nexport default (options: RemoveDeclarationsOptions): Plugin => {\n return {\n postcssPlugin: 'postcss-remove-declarations',\n\n Once(root: Root) {\n const remove = options.remove ?? {}\n\n root.walkRules((rule) => {\n let toRemove = remove[normalizeSelector(rule.selector)]\n\n if (!toRemove) return\n\n // Remove the entire rule\n if (toRemove === '*') {\n rule.remove()\n return\n }\n\n // Normalise a bare string into an array\n if (typeof toRemove === 'string') {\n toRemove = [toRemove]\n }\n\n if (Array.isArray(toRemove)) {\n const props = toRemove as string[]\n rule.walkDecls((decl) => {\n if (props.includes(decl.prop)) decl.remove()\n })\n } else if (typeof toRemove === 'object') {\n // Object: match both property and value\n const map = toRemove as Record<string, string>\n rule.walkDecls((decl) => {\n if (!(decl.prop in map)) return\n\n let expected = map[decl.prop]\n const requireNonImportant = expected.endsWith(IMPORTANT)\n\n if (requireNonImportant) {\n expected = expected.slice(0, -IMPORTANT.length).trim()\n }\n\n if (decl.value !== expected) return\n if (decl.important && requireNonImportant) return\n\n decl.remove()\n })\n }\n\n // Remove the rule if all declarations were removed\n if (rule.nodes?.length === 0) {\n rule.remove()\n }\n })\n },\n }\n}\n\nexport const postcss = true\n"],"mappings":";AAoCA,MAAM,YAAY;AAElB,SAAS,kBAAkB,UAA0B;CACnD,OAAO,SAAS,QAAQ,kBAAkB,EAAE;AAC9C;AAEA,IAAA,8BAAgB,YAA+C;CAC7D,OAAO;EACL,eAAe;EAEf,KAAK,MAAY;GACf,MAAM,SAAS,QAAQ,UAAU,CAAC;GAElC,KAAK,WAAW,SAAS;IACvB,IAAI,WAAW,OAAO,kBAAkB,KAAK,QAAQ;IAErD,IAAI,CAAC,UAAU;IAGf,IAAI,aAAa,KAAK;KACpB,KAAK,OAAO;KACZ;IACF;IAGA,IAAI,OAAO,aAAa,UACtB,WAAW,CAAC,QAAQ;IAGtB,IAAI,MAAM,QAAQ,QAAQ,GAAG;KAC3B,MAAM,QAAQ;KACd,KAAK,WAAW,SAAS;MACvB,IAAI,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,OAAO;KAC7C,CAAC;IACH,OAAO,IAAI,OAAO,aAAa,UAAU;KAEvC,MAAM,MAAM;KACZ,KAAK,WAAW,SAAS;MACvB,IAAI,EAAE,KAAK,QAAQ,MAAM;MAEzB,IAAI,WAAW,IAAI,KAAK;MACxB,MAAM,sBAAsB,SAAS,SAAS,SAAS;MAEvD,IAAI,qBACF,WAAW,SAAS,MAAM,GAAG,GAAiB,CAAC,CAAC,KAAK;MAGvD,IAAI,KAAK,UAAU,UAAU;MAC7B,IAAI,KAAK,aAAa,qBAAqB;MAE3C,KAAK,OAAO;KACd,CAAC;IACH;IAGA,IAAI,KAAK,OAAO,WAAW,GACzB,KAAK,OAAO;GAEhB,CAAC;EACH;CACF;AACF;AAEA,MAAa,UAAU"}
@@ -1 +1 @@
1
- {"version":3,"file":"resolveProps.d.ts","names":[],"sources":["../../../src/plugins/postcss/resolveProps.ts"],"mappings":";;;cAiByD,QAAA,QA+FtC,MAAM;AAAA,cAqJZ,OAAA"}
1
+ {"version":3,"file":"resolveProps.d.ts","names":[],"sources":["../../../src/plugins/postcss/resolveProps.ts"],"mappings":";;;cAiBsE,QAAA,QA+FnD,MAAM;AAAA,cA+IZ,OAAA"}
@@ -143,9 +143,6 @@ var resolveProps_default = () => {
143
143
  root.walkRules((rule) => {
144
144
  if (!isRootRule(rule)) return;
145
145
  if (rule.parent?.type !== "root" && !isInLayer(rule)) return;
146
- rule.each((node) => {
147
- if (node.type === "decl" && node.prop.startsWith("--")) node.remove();
148
- });
149
146
  if (rule.nodes?.length === 0) rule.remove();
150
147
  });
151
148
  }
@@ -1 +1 @@
1
- {"version":3,"file":"resolveProps.js","names":[],"sources":["../../../src/plugins/postcss/resolveProps.ts"],"sourcesContent":["/**\n * postcss-resolve-props\n *\n * Resolves CSS custom properties (var() references) and removes\n * the consumed declarations, producing flat CSS for email inlining.\n *\n * Resolution order per var():\n * 1. Local declaration in the same rule\n * 2. :root declaration (top-level only, not inside @media)\n * 3. @property initial-value (Tailwind v4 composable defaults)\n * 4. Fallback value from the var() itself\n *\n * Handles nested var() in both values and fallbacks.\n * Replaces postcss-custom-properties with correct support for\n * local var declarations (Tailwind composable pattern, user CSS).\n */\n\nimport type { AtRule, Plugin, Root, Rule } from 'postcss'\n\n/** Check if a rule's selector includes :root */\nfunction isRootRule(rule: Rule): boolean {\n const sel = rule.selector\n return sel === ':root' || sel.includes(':root')\n}\n\n/** Check if a rule is inside @layer (not @media or other at-rules) */\nfunction isInLayer(rule: Rule): boolean {\n return rule.parent?.type === 'atrule' && (rule.parent as any).name === 'layer'\n}\n\n/**\n * Parse the first var() call in a string by counting parens.\n * Returns null if no var() found.\n */\nfunction findVar(value: string, startFrom = 0): {\n start: number\n end: number\n name: string\n fallback: string | undefined\n} | null {\n const idx = value.indexOf('var(', startFrom)\n if (idx === -1) return null\n\n let depth = 1\n let i = idx + 4\n let commaIdx = -1\n\n while (i < value.length && depth > 0) {\n const ch = value.charCodeAt(i)\n if (ch === 40 /* ( */) depth++\n else if (ch === 41 /* ) */) {\n if (--depth === 0) break\n } else if (ch === 44 /* , */ && depth === 1 && commaIdx === -1) {\n commaIdx = i\n }\n i++\n }\n\n if (depth !== 0) return null\n\n const name = commaIdx === -1\n ? value.slice(idx + 4, i).trim()\n : value.slice(idx + 4, commaIdx).trim()\n\n const fallback = commaIdx === -1\n ? undefined\n : value.slice(commaIdx + 1, i)\n\n return { start: idx, end: i + 1, name, fallback }\n}\n\n/**\n * Resolve all var() references in a value string.\n * Iterates until no resolvable var() remain.\n */\nfunction resolveValue(\n value: string,\n localVars: Map<string, string>,\n rootVars: Map<string, string>,\n propertyDefaults?: Map<string, string>,\n): string {\n let result = value\n let safety = 10\n\n while (safety-- > 0) {\n const v = findVar(result)\n if (!v) break\n\n const resolved = localVars.get(v.name) ?? rootVars.get(v.name) ?? propertyDefaults?.get(v.name)\n\n if (resolved !== undefined) {\n result = result.slice(0, v.start) + resolved + result.slice(v.end)\n } else if (v.fallback !== undefined) {\n result = result.slice(0, v.start) + v.fallback.trim() + result.slice(v.end)\n } else {\n /**\n * Unresolvable with no fallback — skip past it to avoid infinite loop.\n * Try to find another var() after this one.\n */\n const next = findVar(result, v.end)\n if (!next) break\n\n // Process the rest of the string from after this var()\n const tail = resolveValue(result.slice(v.end), localVars, rootVars, propertyDefaults)\n result = result.slice(0, v.end) + tail\n break\n }\n }\n\n return result\n}\n\nexport default (): Plugin => {\n return {\n postcssPlugin: 'postcss-resolve-props',\n\n Once(root: Root) {\n // Pass 0: collect @property initial-value defaults (Tailwind v4 composable pattern)\n const propertyDefaults = new Map<string, string>()\n\n root.walkAtRules('property', (rule: AtRule) => {\n const name = rule.params.trim()\n if (!name.startsWith('--')) return\n\n rule.walkDecls('initial-value', (decl) => {\n propertyDefaults.set(name, decl.value)\n })\n })\n\n /**\n * Pass 1: collect :root vars (top-level and inside @layer). Skip\n * :root inside @media — those are for dark mode and should\n * stay.\n */\n const rootVars = new Map<string, string>()\n\n root.walkRules((rule) => {\n if (!isRootRule(rule)) return\n\n /**\n * Allow :root at top level or inside @layer (Tailwind theme vars).\n * Skip :root inside @media or other non-layer at-rules.\n */\n if (rule.parent?.type !== 'root' && !isInLayer(rule)) return\n\n rule.each((node) => {\n if (node.type === 'decl' && node.prop.startsWith('--')) {\n rootVars.set(node.prop, node.value)\n }\n })\n })\n\n // Resolve :root vars referencing other :root vars\n if (rootVars.size > 0) {\n let changed = true\n let iterations = 5\n\n while (changed && iterations-- > 0) {\n changed = false\n for (const [name, value] of rootVars) {\n if (!value.includes('var(')) continue\n const resolved = resolveValue(value, rootVars, rootVars, propertyDefaults)\n if (resolved !== value) {\n rootVars.set(name, resolved)\n changed = true\n }\n }\n }\n }\n\n // Pass 2: resolve var() in all rules\n root.walkRules((rule) => {\n /**\n * Skip :root inside @media — those vars are for dark mode etc. and\n * must stay in the <style> tag as-is. Allow :root at top\n * level and inside @layer (processed in pass 3).\n */\n if (isRootRule(rule)) {\n if (rule.parent?.type !== 'root' && !isInLayer(rule)) return\n }\n\n // Collect local --* declarations (walk into nested @media etc.)\n const localVars = new Map<string, string>()\n const localDecls: Declaration[] = []\n let hasVarRefs = false\n\n rule.walk((node) => {\n if (node.type !== 'decl') return\n\n if (node.prop.startsWith('--')) {\n localDecls.push(node)\n } else if (node.value.includes('var(')) {\n hasVarRefs = true\n }\n })\n\n // Skip rules with no var() references and no local vars to clean up\n if (!hasVarRefs && localDecls.length === 0) return\n\n // Build local vars map — resolve values that reference other locals or :root\n for (const decl of localDecls) {\n const value = decl.value.includes('var(')\n ? resolveValue(decl.value, localVars, rootVars, propertyDefaults)\n : decl.value\n\n localVars.set(decl.prop, value)\n }\n\n // Resolve var() in non-custom-property declarations (walk into nested @media etc.)\n if (hasVarRefs) {\n rule.walk((node) => {\n if (node.type !== 'decl') return\n if (node.prop.startsWith('--')) return\n if (!node.value.includes('var(')) return\n\n const resolved = resolveValue(node.value, localVars, rootVars, propertyDefaults)\n\n if (resolved !== node.value) {\n // Clean up: collapse whitespace, trim\n const cleaned = resolved.replace(/ +/g, ' ').trim()\n\n if (cleaned) {\n node.value = cleaned\n } else {\n // Value resolved to empty — remove the declaration\n node.remove()\n }\n }\n })\n }\n\n // Remove local --* declarations (consumed or no longer needed)\n for (const decl of localDecls) {\n decl.remove()\n }\n })\n\n // Remove @property rules (not supported in email clients)\n root.walkAtRules('property', (rule) => {\n rule.remove()\n })\n\n // Pass 3: clean up :root (same scope as pass 1)\n root.walkRules((rule) => {\n if (!isRootRule(rule)) return\n if (rule.parent?.type !== 'root' && !isInLayer(rule)) return\n\n rule.each((node) => {\n if (node.type === 'decl' && node.prop.startsWith('--')) {\n node.remove()\n }\n })\n\n if (rule.nodes?.length === 0) {\n rule.remove()\n }\n })\n },\n }\n}\n\nexport const postcss = true\n"],"mappings":"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"}
1
+ {"version":3,"file":"resolveProps.js","names":[],"sources":["../../../src/plugins/postcss/resolveProps.ts"],"sourcesContent":["/**\n * postcss-resolve-props\n *\n * Resolves CSS custom properties (var() references) and removes\n * the consumed declarations, producing flat CSS for email inlining.\n *\n * Resolution order per var():\n * 1. Local declaration in the same rule\n * 2. :root declaration (top-level only, not inside @media)\n * 3. @property initial-value (Tailwind v4 composable defaults)\n * 4. Fallback value from the var() itself\n *\n * Handles nested var() in both values and fallbacks.\n * Replaces postcss-custom-properties with correct support for\n * local var declarations (Tailwind composable pattern, user CSS).\n */\n\nimport type { AtRule, Declaration, Plugin, Root, Rule } from 'postcss'\n\n/** Check if a rule's selector includes :root */\nfunction isRootRule(rule: Rule): boolean {\n const sel = rule.selector\n return sel === ':root' || sel.includes(':root')\n}\n\n/** Check if a rule is inside @layer (not @media or other at-rules) */\nfunction isInLayer(rule: Rule): boolean {\n return rule.parent?.type === 'atrule' && (rule.parent as any).name === 'layer'\n}\n\n/**\n * Parse the first var() call in a string by counting parens.\n * Returns null if no var() found.\n */\nfunction findVar(value: string, startFrom = 0): {\n start: number\n end: number\n name: string\n fallback: string | undefined\n} | null {\n const idx = value.indexOf('var(', startFrom)\n if (idx === -1) return null\n\n let depth = 1\n let i = idx + 4\n let commaIdx = -1\n\n while (i < value.length && depth > 0) {\n const ch = value.charCodeAt(i)\n if (ch === 40 /* ( */) depth++\n else if (ch === 41 /* ) */) {\n if (--depth === 0) break\n } else if (ch === 44 /* , */ && depth === 1 && commaIdx === -1) {\n commaIdx = i\n }\n i++\n }\n\n if (depth !== 0) return null\n\n const name = commaIdx === -1\n ? value.slice(idx + 4, i).trim()\n : value.slice(idx + 4, commaIdx).trim()\n\n const fallback = commaIdx === -1\n ? undefined\n : value.slice(commaIdx + 1, i)\n\n return { start: idx, end: i + 1, name, fallback }\n}\n\n/**\n * Resolve all var() references in a value string.\n * Iterates until no resolvable var() remain.\n */\nfunction resolveValue(\n value: string,\n localVars: Map<string, string>,\n rootVars: Map<string, string>,\n propertyDefaults?: Map<string, string>,\n): string {\n let result = value\n let safety = 10\n\n while (safety-- > 0) {\n const v = findVar(result)\n if (!v) break\n\n const resolved = localVars.get(v.name) ?? rootVars.get(v.name) ?? propertyDefaults?.get(v.name)\n\n if (resolved !== undefined) {\n result = result.slice(0, v.start) + resolved + result.slice(v.end)\n } else if (v.fallback !== undefined) {\n result = result.slice(0, v.start) + v.fallback.trim() + result.slice(v.end)\n } else {\n /**\n * Unresolvable with no fallback — skip past it to avoid infinite loop.\n * Try to find another var() after this one.\n */\n const next = findVar(result, v.end)\n if (!next) break\n\n // Process the rest of the string from after this var()\n const tail = resolveValue(result.slice(v.end), localVars, rootVars, propertyDefaults)\n result = result.slice(0, v.end) + tail\n break\n }\n }\n\n return result\n}\n\nexport default (): Plugin => {\n return {\n postcssPlugin: 'postcss-resolve-props',\n\n Once(root: Root) {\n // Pass 0: collect @property initial-value defaults (Tailwind v4 composable pattern)\n const propertyDefaults = new Map<string, string>()\n\n root.walkAtRules('property', (rule: AtRule) => {\n const name = rule.params.trim()\n if (!name.startsWith('--')) return\n\n rule.walkDecls('initial-value', (decl) => {\n propertyDefaults.set(name, decl.value)\n })\n })\n\n /**\n * Pass 1: collect :root vars (top-level and inside @layer). Skip\n * :root inside @media — those are for dark mode and should\n * stay.\n */\n const rootVars = new Map<string, string>()\n\n root.walkRules((rule) => {\n if (!isRootRule(rule)) return\n\n /**\n * Allow :root at top level or inside @layer (Tailwind theme vars).\n * Skip :root inside @media or other non-layer at-rules.\n */\n if (rule.parent?.type !== 'root' && !isInLayer(rule)) return\n\n rule.each((node) => {\n if (node.type === 'decl' && node.prop.startsWith('--')) {\n rootVars.set(node.prop, node.value)\n }\n })\n })\n\n // Resolve :root vars referencing other :root vars\n if (rootVars.size > 0) {\n let changed = true\n let iterations = 5\n\n while (changed && iterations-- > 0) {\n changed = false\n for (const [name, value] of rootVars) {\n if (!value.includes('var(')) continue\n const resolved = resolveValue(value, rootVars, rootVars, propertyDefaults)\n if (resolved !== value) {\n rootVars.set(name, resolved)\n changed = true\n }\n }\n }\n }\n\n // Pass 2: resolve var() in all rules\n root.walkRules((rule) => {\n /**\n * Skip :root inside @media — those vars are for dark mode etc. and\n * must stay in the <style> tag as-is. Allow :root at top\n * level and inside @layer (processed in pass 3).\n */\n if (isRootRule(rule)) {\n if (rule.parent?.type !== 'root' && !isInLayer(rule)) return\n }\n\n // Collect local --* declarations (walk into nested @media etc.)\n const localVars = new Map<string, string>()\n const localDecls: Declaration[] = []\n let hasVarRefs = false\n\n rule.walk((node) => {\n if (node.type !== 'decl') return\n\n if (node.prop.startsWith('--')) {\n localDecls.push(node)\n } else if (node.value.includes('var(')) {\n hasVarRefs = true\n }\n })\n\n // Skip rules with no var() references and no local vars to clean up\n if (!hasVarRefs && localDecls.length === 0) return\n\n // Build local vars map — resolve values that reference other locals or :root\n for (const decl of localDecls) {\n const value = decl.value.includes('var(')\n ? resolveValue(decl.value, localVars, rootVars, propertyDefaults)\n : decl.value\n\n localVars.set(decl.prop, value)\n }\n\n // Resolve var() in non-custom-property declarations (walk into nested @media etc.)\n if (hasVarRefs) {\n rule.walk((node) => {\n if (node.type !== 'decl') return\n if (node.prop.startsWith('--')) return\n if (!node.value.includes('var(')) return\n\n const resolved = resolveValue(node.value, localVars, rootVars, propertyDefaults)\n\n if (resolved !== node.value) {\n // Clean up: collapse whitespace, trim\n const cleaned = resolved.replace(/ +/g, ' ').trim()\n\n if (cleaned) {\n node.value = cleaned\n } else {\n // Value resolved to empty — remove the declaration\n node.remove()\n }\n }\n })\n }\n\n // Remove local --* declarations (consumed or no longer needed)\n for (const decl of localDecls) {\n decl.remove()\n }\n })\n\n // Remove @property rules (not supported in email clients)\n root.walkAtRules('property', (rule) => {\n rule.remove()\n })\n\n // Pass 3: drop now-empty :root rules (their vars were removed in pass 2)\n root.walkRules((rule) => {\n if (!isRootRule(rule)) return\n if (rule.parent?.type !== 'root' && !isInLayer(rule)) return\n\n if (rule.nodes?.length === 0) {\n rule.remove()\n }\n })\n },\n }\n}\n\nexport const postcss = true\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tailwindCleanup.js","names":[],"sources":["../../../src/plugins/postcss/tailwindCleanup.ts"],"sourcesContent":["import postcss from 'postcss'\nimport type { MaizzleConfig } from '../../types/config.ts'\n\nconst DEFAULT_SELECTORS = [':host', ':lang']\nconst DEFAULT_AT_RULES = ['layer', 'property']\n\n/**\n * Split a selector list on top-level commas only.\n *\n * Commas inside parenthesised groups like `:not(:is(:lang(ae), :lang(ar)))`\n * are not treated as selector separators.\n */\nfunction splitSelector(selector: string): string[] {\n const parts: string[] = []\n let depth = 0\n let start = 0\n\n for (let i = 0; i < selector.length; i++) {\n const ch = selector[i]\n if (ch === '(') depth++\n else if (ch === ')') depth--\n else if (ch === ',' && depth === 0) {\n parts.push(selector.slice(start, i).trim())\n start = i + 1\n }\n }\n\n parts.push(selector.slice(start).trim())\n\n return parts.filter(Boolean)\n}\n\n/**\n * Removes CSS rules whose every comma-separated selector part starts with\n * one of the configured prefixes (e.g. ':host', ':lang'). Rules with mixed\n * selectors have the unwanted parts stripped.\n *\n * Also removes entire at-rules by name (e.g. '@layer', '@property').\n *\n * Intended to clean up Tailwind's compiled output after lightningcss has\n * flattened all modern CSS syntax.\n */\nexport function tailwindCleanup(config: MaizzleConfig): postcss.Plugin[] {\n const selectors: string[] = config.postcss?.removeSelectors ?? DEFAULT_SELECTORS\n const atRules: string[] = config.postcss?.removeAtRules ?? DEFAULT_AT_RULES\n\n return [\n {\n postcssPlugin: 'tailwind-cleanup-selectors',\n Rule(rule) {\n const parts = splitSelector(rule.selector)\n const kept = parts.filter(p => !selectors.some(s => p === s || p.includes(`${s}(`)))\n if (kept.length === 0) {\n rule.remove()\n } else if (kept.length < parts.length) {\n rule.selector = kept.join(', ')\n }\n },\n },\n {\n postcssPlugin: 'tailwind-cleanup-at-rules',\n AtRule(rule) {\n if (!atRules.includes(rule.name)) return\n\n if (rule.nodes?.length) {\n // Unwrap: keep children, remove the at-rule wrapper\n rule.replaceWith(rule.nodes)\n } else {\n rule.remove()\n }\n },\n },\n {\n postcssPlugin: 'tailwind-cleanup-text-decoration',\n Declaration(decl) {\n if (decl.prop === 'text-decoration-line') {\n decl.prop = 'text-decoration'\n }\n },\n },\n ]\n}\n"],"mappings":";AAGA,MAAM,oBAAoB,CAAC,SAAS,OAAO;AAC3C,MAAM,mBAAmB,CAAC,SAAS,UAAU;;;;;;;AAQ7C,SAAS,cAAc,UAA4B;CACjD,MAAM,QAAkB,CAAC;CACzB,IAAI,QAAQ;CACZ,IAAI,QAAQ;CAEZ,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,KAAK,SAAS;EACpB,IAAI,OAAO,KAAK;OACX,IAAI,OAAO,KAAK;OAChB,IAAI,OAAO,OAAO,UAAU,GAAG;GAClC,MAAM,KAAK,SAAS,MAAM,OAAO,CAAC,EAAE,KAAK,CAAC;GAC1C,QAAQ,IAAI;EACd;CACF;CAEA,MAAM,KAAK,SAAS,MAAM,KAAK,EAAE,KAAK,CAAC;CAEvC,OAAO,MAAM,OAAO,OAAO;AAC7B;;;;;;;;;;;AAYA,SAAgB,gBAAgB,QAAyC;CACvE,MAAM,YAAsB,OAAO,SAAS,mBAAmB;CAC/D,MAAM,UAAoB,OAAO,SAAS,iBAAiB;CAE3D,OAAO;EACL;GACE,eAAe;GACf,KAAK,MAAM;IACT,MAAM,QAAQ,cAAc,KAAK,QAAQ;IACzC,MAAM,OAAO,MAAM,QAAO,MAAK,CAAC,UAAU,MAAK,MAAK,MAAM,KAAK,EAAE,SAAS,GAAG,EAAE,EAAE,CAAC,CAAC;IACnF,IAAI,KAAK,WAAW,GAClB,KAAK,OAAO;SACP,IAAI,KAAK,SAAS,MAAM,QAC7B,KAAK,WAAW,KAAK,KAAK,IAAI;GAElC;EACF;EACA;GACE,eAAe;GACf,OAAO,MAAM;IACX,IAAI,CAAC,QAAQ,SAAS,KAAK,IAAI,GAAG;IAElC,IAAI,KAAK,OAAO,QAEd,KAAK,YAAY,KAAK,KAAK;SAE3B,KAAK,OAAO;GAEhB;EACF;EACA;GACE,eAAe;GACf,YAAY,MAAM;IAChB,IAAI,KAAK,SAAS,wBAChB,KAAK,OAAO;GAEhB;EACF;CACF;AACF"}
1
+ {"version":3,"file":"tailwindCleanup.js","names":[],"sources":["../../../src/plugins/postcss/tailwindCleanup.ts"],"sourcesContent":["import postcss from 'postcss'\nimport type { MaizzleConfig } from '../../types/config.ts'\n\nconst DEFAULT_SELECTORS = [':host', ':lang']\nconst DEFAULT_AT_RULES = ['layer', 'property']\n\n/**\n * Split a selector list on top-level commas only.\n *\n * Commas inside parenthesised groups like `:not(:is(:lang(ae), :lang(ar)))`\n * are not treated as selector separators.\n */\nfunction splitSelector(selector: string): string[] {\n const parts: string[] = []\n let depth = 0\n let start = 0\n\n for (let i = 0; i < selector.length; i++) {\n const ch = selector[i]\n if (ch === '(') depth++\n else if (ch === ')') depth--\n else if (ch === ',' && depth === 0) {\n parts.push(selector.slice(start, i).trim())\n start = i + 1\n }\n }\n\n parts.push(selector.slice(start).trim())\n\n return parts.filter(Boolean)\n}\n\n/**\n * Removes CSS rules whose every comma-separated selector part starts with\n * one of the configured prefixes (e.g. ':host', ':lang'). Rules with mixed\n * selectors have the unwanted parts stripped.\n *\n * Also removes entire at-rules by name (e.g. '@layer', '@property').\n *\n * Intended to clean up Tailwind's compiled output after lightningcss has\n * flattened all modern CSS syntax.\n */\nexport function tailwindCleanup(config: MaizzleConfig): postcss.Plugin[] {\n const selectors: string[] = config.postcss?.removeSelectors ?? DEFAULT_SELECTORS\n const atRules: string[] = config.postcss?.removeAtRules ?? DEFAULT_AT_RULES\n\n return [\n {\n postcssPlugin: 'tailwind-cleanup-selectors',\n Rule(rule) {\n const parts = splitSelector(rule.selector)\n const kept = parts.filter(p => !selectors.some(s => p === s || p.includes(`${s}(`)))\n if (kept.length === 0) {\n rule.remove()\n } else if (kept.length < parts.length) {\n rule.selector = kept.join(', ')\n }\n },\n },\n {\n postcssPlugin: 'tailwind-cleanup-at-rules',\n AtRule(rule) {\n if (!atRules.includes(rule.name)) return\n\n if (rule.nodes?.length) {\n // Unwrap: keep children, remove the at-rule wrapper\n rule.replaceWith(rule.nodes)\n } else {\n rule.remove()\n }\n },\n },\n {\n postcssPlugin: 'tailwind-cleanup-text-decoration',\n Declaration(decl) {\n if (decl.prop === 'text-decoration-line') {\n decl.prop = 'text-decoration'\n }\n },\n },\n ]\n}\n"],"mappings":";AAGA,MAAM,oBAAoB,CAAC,SAAS,OAAO;AAC3C,MAAM,mBAAmB,CAAC,SAAS,UAAU;;;;;;;AAQ7C,SAAS,cAAc,UAA4B;CACjD,MAAM,QAAkB,CAAC;CACzB,IAAI,QAAQ;CACZ,IAAI,QAAQ;CAEZ,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,KAAK,SAAS;EACpB,IAAI,OAAO,KAAK;OACX,IAAI,OAAO,KAAK;OAChB,IAAI,OAAO,OAAO,UAAU,GAAG;GAClC,MAAM,KAAK,SAAS,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;GAC1C,QAAQ,IAAI;EACd;CACF;CAEA,MAAM,KAAK,SAAS,MAAM,KAAK,CAAC,CAAC,KAAK,CAAC;CAEvC,OAAO,MAAM,OAAO,OAAO;AAC7B;;;;;;;;;;;AAYA,SAAgB,gBAAgB,QAAyC;CACvE,MAAM,YAAsB,OAAO,SAAS,mBAAmB;CAC/D,MAAM,UAAoB,OAAO,SAAS,iBAAiB;CAE3D,OAAO;EACL;GACE,eAAe;GACf,KAAK,MAAM;IACT,MAAM,QAAQ,cAAc,KAAK,QAAQ;IACzC,MAAM,OAAO,MAAM,QAAO,MAAK,CAAC,UAAU,MAAK,MAAK,MAAM,KAAK,EAAE,SAAS,GAAG,EAAE,EAAE,CAAC,CAAC;IACnF,IAAI,KAAK,WAAW,GAClB,KAAK,OAAO;SACP,IAAI,KAAK,SAAS,MAAM,QAC7B,KAAK,WAAW,KAAK,KAAK,IAAI;GAElC;EACF;EACA;GACE,eAAe;GACf,OAAO,MAAM;IACX,IAAI,CAAC,QAAQ,SAAS,KAAK,IAAI,GAAG;IAElC,IAAI,KAAK,OAAO,QAEd,KAAK,YAAY,KAAK,KAAK;SAE3B,KAAK,OAAO;GAEhB;EACF;EACA;GACE,eAAe;GACf,YAAY,MAAM;IAChB,IAAI,KAAK,SAAS,wBAChB,KAAK,OAAO;GAEhB;EACF;CACF;AACF"}
package/dist/prepare.js CHANGED
@@ -31,7 +31,7 @@ async function prepare(options = {}) {
31
31
  } finally {
32
32
  await renderer.close();
33
33
  }
34
- const dtsDir = isLaravel() ? resolve(process.cwd(), "resources/js/types/maizzle") : resolve(config.root, ".maizzle");
34
+ const dtsDir = isLaravel() ? resolve(process.cwd(), "resources/js/types/maizzle") : resolve(config.root ?? process.cwd(), ".maizzle");
35
35
  const displayPath = relative(process.cwd(), dtsDir) || dtsDir;
36
36
  spinner.stopAndPersist({
37
37
  symbol: "✅",
@@ -1 +1 @@
1
- {"version":3,"file":"prepare.js","names":[],"sources":["../src/prepare.ts"],"sourcesContent":["import { relative, resolve } from 'node:path'\nimport ora from 'ora'\nimport { resolveConfig } from './config/index.ts'\nimport { createRenderer } from './render/createRenderer.ts'\nimport { isLaravel } from './utils/detect.ts'\nimport { normalizeComponentSources } from './utils/componentSources.ts'\n\nexport interface PrepareOptions {\n /** Path to a Maizzle config file. */\n config?: string\n}\n\n/**\n * Generate IDE type definitions in `.maizzle/`\n * (`auto-imports.d.ts` and `components.d.ts`).\n *\n * Intended as a `postinstall` step so editors get autocompletion before the\n * user runs `dev` or `build`. Spins up the renderer with `dts: true`, runs\n * a trivial render to trigger the unplugin scans, then shuts down.\n */\nexport async function prepare(options: PrepareOptions = {}): Promise<void> {\n const spinner = ora({ text: 'Generating types...', spinner: 'circleHalves' }).start()\n\n const config = await resolveConfig(options.config)\n\n const renderer = await createRenderer({\n dts: true,\n markdown: config.markdown,\n root: config.root,\n componentDirs: normalizeComponentSources(config.components?.source, process.cwd()),\n vite: config.vite,\n })\n\n try {\n await renderer.render('<template><div></div></template>', config)\n } finally {\n await renderer.close()\n }\n\n const dtsDir = isLaravel()\n ? resolve(process.cwd(), 'resources/js/types/maizzle')\n : resolve(config.root, '.maizzle')\n const displayPath = relative(process.cwd(), dtsDir) || dtsDir\n\n spinner.stopAndPersist({\n symbol: '✅',\n text: `Types generated in ${displayPath}`,\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;AAoBA,eAAsB,QAAQ,UAA0B,CAAC,GAAkB;CACzE,MAAM,UAAU,IAAI;EAAE,MAAM;EAAuB,SAAS;CAAe,CAAC,EAAE,MAAM;CAEpF,MAAM,SAAS,MAAM,cAAc,QAAQ,MAAM;CAEjD,MAAM,WAAW,MAAM,eAAe;EACpC,KAAK;EACL,UAAU,OAAO;EACjB,MAAM,OAAO;EACb,eAAe,0BAA0B,OAAO,YAAY,QAAQ,QAAQ,IAAI,CAAC;EACjF,MAAM,OAAO;CACf,CAAC;CAED,IAAI;EACF,MAAM,SAAS,OAAO,oCAAoC,MAAM;CAClE,UAAU;EACR,MAAM,SAAS,MAAM;CACvB;CAEA,MAAM,SAAS,UAAU,IACrB,QAAQ,QAAQ,IAAI,GAAG,4BAA4B,IACnD,QAAQ,OAAO,MAAM,UAAU;CACnC,MAAM,cAAc,SAAS,QAAQ,IAAI,GAAG,MAAM,KAAK;CAEvD,QAAQ,eAAe;EACrB,QAAQ;EACR,MAAM,sBAAsB;CAC9B,CAAC;AACH"}
1
+ {"version":3,"file":"prepare.js","names":[],"sources":["../src/prepare.ts"],"sourcesContent":["import { relative, resolve } from 'node:path'\nimport ora from 'ora'\nimport { resolveConfig } from './config/index.ts'\nimport { createRenderer } from './render/createRenderer.ts'\nimport { isLaravel } from './utils/detect.ts'\nimport { normalizeComponentSources } from './utils/componentSources.ts'\n\nexport interface PrepareOptions {\n /** Path to a Maizzle config file. */\n config?: string\n}\n\n/**\n * Generate IDE type definitions in `.maizzle/`\n * (`auto-imports.d.ts` and `components.d.ts`).\n *\n * Intended as a `postinstall` step so editors get autocompletion before the\n * user runs `dev` or `build`. Spins up the renderer with `dts: true`, runs\n * a trivial render to trigger the unplugin scans, then shuts down.\n */\nexport async function prepare(options: PrepareOptions = {}): Promise<void> {\n const spinner = ora({ text: 'Generating types...', spinner: 'circleHalves' }).start()\n\n const config = await resolveConfig(options.config)\n\n const renderer = await createRenderer({\n dts: true,\n markdown: config.markdown,\n root: config.root,\n componentDirs: normalizeComponentSources(config.components?.source, process.cwd()),\n vite: config.vite,\n })\n\n try {\n await renderer.render('<template><div></div></template>', config)\n } finally {\n await renderer.close()\n }\n\n const dtsDir = isLaravel()\n ? resolve(process.cwd(), 'resources/js/types/maizzle')\n : resolve(config.root ?? process.cwd(), '.maizzle')\n const displayPath = relative(process.cwd(), dtsDir) || dtsDir\n\n spinner.stopAndPersist({\n symbol: '✅',\n text: `Types generated in ${displayPath}`,\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;AAoBA,eAAsB,QAAQ,UAA0B,CAAC,GAAkB;CACzE,MAAM,UAAU,IAAI;EAAE,MAAM;EAAuB,SAAS;CAAe,CAAC,CAAC,CAAC,MAAM;CAEpF,MAAM,SAAS,MAAM,cAAc,QAAQ,MAAM;CAEjD,MAAM,WAAW,MAAM,eAAe;EACpC,KAAK;EACL,UAAU,OAAO;EACjB,MAAM,OAAO;EACb,eAAe,0BAA0B,OAAO,YAAY,QAAQ,QAAQ,IAAI,CAAC;EACjF,MAAM,OAAO;CACf,CAAC;CAED,IAAI;EACF,MAAM,SAAS,OAAO,oCAAoC,MAAM;CAClE,UAAU;EACR,MAAM,SAAS,MAAM;CACvB;CAEA,MAAM,SAAS,UAAU,IACrB,QAAQ,QAAQ,IAAI,GAAG,4BAA4B,IACnD,QAAQ,OAAO,QAAQ,QAAQ,IAAI,GAAG,UAAU;CACpD,MAAM,cAAc,SAAS,QAAQ,IAAI,GAAG,MAAM,KAAK;CAEvD,QAAQ,eAAe;EACrB,QAAQ;EACR,MAAM,sBAAsB;CAC9B,CAAC;AACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"active.d.ts","names":[],"sources":["../../src/render/active.ts"],"mappings":";;;iBAIgB,iBAAA,CAAkB,QAAyB,EAAf,QAAQ;AAAA,iBAIpC,iBAAA,CAAA,GAAqB,QAAQ"}
1
+ {"version":3,"file":"active.d.ts","names":[],"sources":["../../src/render/active.ts"],"mappings":";;;iBAIgB,iBAAA,CAAkB,QAAyB,EAAf,QAAQ;AAAA,iBAIpC,iBAAA,IAAqB,QAAQ"}
@@ -0,0 +1,49 @@
1
+ import { EventManager } from "../events/index.js";
2
+ import { MaizzleConfig } from "../types/config.js";
3
+ import { Renderer } from "./createRenderer.js";
4
+
5
+ //#region src/render/buildTemplate.d.ts
6
+ interface BuildTemplateContext {
7
+ config: MaizzleConfig;
8
+ renderer: Renderer;
9
+ events: EventManager;
10
+ outputPath: string;
11
+ outputExtension: string;
12
+ contentBase: string;
13
+ }
14
+ interface BuildTemplateResult {
15
+ /** Output files written for this template (html + optional plaintext). */
16
+ files: string[];
17
+ /**
18
+ * Number of SFC-registered `afterBuild` handlers seen while rendering. They
19
+ * only fire once at end of build on the main thread, so a worker can't run
20
+ * them — the count lets the orchestrator warn instead of silently dropping.
21
+ */
22
+ sfcAfterBuildCount: number;
23
+ }
24
+ /**
25
+ * Render a single template through the full pipeline and write its output.
26
+ *
27
+ * Shared by the sequential build loop and the parallel build worker so both
28
+ * paths produce byte-identical output. `events` is the manager the per-template
29
+ * events fire on (config handlers registered via `registerConfig`, SFC handlers
30
+ * registered here from the render). The caller owns build-scoped events
31
+ * (`beforeCreate`/`afterBuild`).
32
+ */
33
+ declare function buildTemplate(templatePath: string, ctx: BuildTemplateContext): Promise<BuildTemplateResult>;
34
+ /**
35
+ * Extract the static (non-glob) prefix from content patterns.
36
+ *
37
+ * For example, `['/abs/path/emails/**\/*.vue']` → `'/abs/path/emails'`
38
+ *
39
+ * Used to strip the content base from template paths so the output preserves
40
+ * only the subdirectory structure.
41
+ *
42
+ * With multiple positive patterns (multi-root setups), returns their common
43
+ * ancestor directory so templates from every root keep a clean relative path.
44
+ */
45
+ declare function computeContentBase(patterns: string[]): string;
46
+ declare function resolveOutputPath(templatePath: string, outputDir: string, extension: string, contentBase: string): string;
47
+ //#endregion
48
+ export { BuildTemplateContext, BuildTemplateResult, buildTemplate, computeContentBase, resolveOutputPath };
49
+ //# sourceMappingURL=buildTemplate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildTemplate.d.ts","names":[],"sources":["../../src/render/buildTemplate.ts"],"mappings":";;;;;UAWiB,oBAAA;EACf,MAAA,EAAQ,aAAA;EACR,QAAA,EAAU,QAAA;EACV,MAAA,EAAQ,YAAA;EACR,UAAA;EACA,eAAA;EACA,WAAA;AAAA;AAAA,UAGe,mBAAA;EANK;EAQpB,KAAA;EAVA;;;;;EAgBA,kBAAkB;AAAA;;;;AAXP;AAGb;;;;AAQoB;iBAYE,aAAA,CACpB,YAAA,UACA,GAAA,EAAK,oBAAA,GACJ,OAAA,CAAQ,mBAAA;;;;;;;;;;;;iBAgHK,kBAAA,CAAmB,QAAkB;AAAA,iBA4BrC,iBAAA,CAAkB,YAAA,UAAsB,SAAA,UAAmB,SAAA,UAAmB,WAAA"}
@@ -0,0 +1,139 @@
1
+ import { runTransformers } from "../transformers/index.js";
2
+ import { createPlaintext } from "../plaintext.js";
3
+ import { stripForHtml, stripForPlaintext } from "../utils/output-markers.js";
4
+ import { _setCurrentTemplate } from "../composables/useCurrentTemplate.js";
5
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { basename, dirname, join, parse, relative, resolve, sep } from "node:path";
7
+ import defu from "defu";
8
+ //#region src/render/buildTemplate.ts
9
+ /**
10
+ * Render a single template through the full pipeline and write its output.
11
+ *
12
+ * Shared by the sequential build loop and the parallel build worker so both
13
+ * paths produce byte-identical output. `events` is the manager the per-template
14
+ * events fire on (config handlers registered via `registerConfig`, SFC handlers
15
+ * registered here from the render). The caller owns build-scoped events
16
+ * (`beforeCreate`/`afterBuild`).
17
+ */
18
+ async function buildTemplate(templatePath, ctx) {
19
+ const { config, renderer, events, outputPath, outputExtension, contentBase } = ctx;
20
+ const absolutePath = resolve(templatePath);
21
+ const parsedPath = parse(absolutePath);
22
+ const template = {
23
+ source: readFileSync(absolutePath, "utf-8"),
24
+ path: parsedPath
25
+ };
26
+ const files = [];
27
+ let sfcAfterBuildCount = 0;
28
+ _setCurrentTemplate(parsedPath);
29
+ try {
30
+ /**
31
+ * Clone config per template so beforeRender mutations (setting a
32
+ * preheader, injecting fetched data, etc.) stay scoped to this template
33
+ * instead of leaking into later ones through the shared config object.
34
+ */
35
+ const renderConfig = defu({}, config);
36
+ const originalSource = template.source;
37
+ await events.fireBeforeRender({
38
+ config: renderConfig,
39
+ template
40
+ });
41
+ const rendered = await renderer.render(absolutePath, renderConfig, template.source !== originalSource ? { source: template.source } : void 0);
42
+ /**
43
+ * Register SFC event handlers collected during render so they take part in
44
+ * the post-render events. Cleared at the end of this call so they don't
45
+ * leak into the next template (afterBuild is the exception — it's never
46
+ * cleared by clearSfcHandlers; see the count above).
47
+ */
48
+ for (const { name, handler } of rendered.sfcEventHandlers) {
49
+ if (name === "afterBuild") sfcAfterBuildCount++;
50
+ events.on(name, handler);
51
+ }
52
+ const templateConfig = rendered.templateConfig;
53
+ let html = await events.fireAfterRender({
54
+ config: templateConfig,
55
+ template,
56
+ html: rendered.html
57
+ });
58
+ const doctype = rendered.doctype ?? templateConfig.doctype ?? "<!DOCTYPE html>";
59
+ if (templateConfig.useTransformers !== false) html = await runTransformers(html, templateConfig, absolutePath, doctype, rendered.tailwindBlocks);
60
+ html = await events.fireAfterTransform({
61
+ config: templateConfig,
62
+ template,
63
+ html
64
+ });
65
+ if (doctype) html = `${doctype}\n${html}`;
66
+ const htmlOut = stripForHtml(html);
67
+ const sfcOutputPath = rendered.outputPath;
68
+ let outputFilePath;
69
+ if (sfcOutputPath) {
70
+ const parsed = parse(resolve(sfcOutputPath));
71
+ const ext = parsed.ext ? parsed.ext.slice(1) : outputExtension;
72
+ outputFilePath = join(parsed.dir, `${parsed.name}.${ext}`);
73
+ } else outputFilePath = resolveOutputPath(templatePath, outputPath, outputExtension, contentBase);
74
+ mkdirSync(dirname(outputFilePath), { recursive: true });
75
+ writeFileSync(outputFilePath, htmlOut);
76
+ files.push(outputFilePath);
77
+ const globalPlaintext = templateConfig.plaintext;
78
+ const sfcPlaintext = rendered.plaintext;
79
+ if (globalPlaintext || sfcPlaintext) {
80
+ const globalCfg = typeof globalPlaintext === "object" ? globalPlaintext : {};
81
+ const stripOptions = defu(sfcPlaintext?.options, globalCfg.options);
82
+ const plaintext = createPlaintext(stripForPlaintext(html), stripOptions);
83
+ const ptExtension = sfcPlaintext?.extension ?? globalCfg.extension ?? "txt";
84
+ let ptOutputPath;
85
+ if (sfcPlaintext?.destination) ptOutputPath = resolveOutputPath(templatePath, resolve(sfcPlaintext.destination), ptExtension, contentBase);
86
+ else if (sfcOutputPath) {
87
+ const parsed = parse(outputFilePath);
88
+ ptOutputPath = join(parsed.dir, `${parsed.name}.${ptExtension}`);
89
+ } else if (globalCfg.destination) ptOutputPath = resolveOutputPath(templatePath, resolve(globalCfg.destination), ptExtension, contentBase);
90
+ else ptOutputPath = resolveOutputPath(templatePath, outputPath, ptExtension, contentBase);
91
+ mkdirSync(dirname(ptOutputPath), { recursive: true });
92
+ writeFileSync(ptOutputPath, plaintext);
93
+ }
94
+ } finally {
95
+ _setCurrentTemplate(void 0);
96
+ events.clearSfcHandlers();
97
+ }
98
+ return {
99
+ files,
100
+ sfcAfterBuildCount
101
+ };
102
+ }
103
+ /**
104
+ * Extract the static (non-glob) prefix from content patterns.
105
+ *
106
+ * For example, `['/abs/path/emails/**\/*.vue']` → `'/abs/path/emails'`
107
+ *
108
+ * Used to strip the content base from template paths so the output preserves
109
+ * only the subdirectory structure.
110
+ *
111
+ * With multiple positive patterns (multi-root setups), returns their common
112
+ * ancestor directory so templates from every root keep a clean relative path.
113
+ */
114
+ function computeContentBase(patterns) {
115
+ const positives = patterns.filter((p) => !p.startsWith("!"));
116
+ return (positives.length > 0 ? positives : patterns).map((pattern) => {
117
+ const staticPart = pattern.split(/[*{?[]/)[0];
118
+ return resolve(staticPart.endsWith("/") ? staticPart : dirname(staticPart));
119
+ }).reduce(commonPath);
120
+ }
121
+ /** Longest common directory path shared by two absolute paths. */
122
+ function commonPath(a, b) {
123
+ const aSegments = a.split(sep);
124
+ const bSegments = b.split(sep);
125
+ const shared = [];
126
+ for (let i = 0; i < Math.min(aSegments.length, bSegments.length); i++) {
127
+ if (aSegments[i] !== bSegments[i]) break;
128
+ shared.push(aSegments[i]);
129
+ }
130
+ return shared.join(sep) || sep;
131
+ }
132
+ function resolveOutputPath(templatePath, outputDir, extension, contentBase) {
133
+ const name = basename(templatePath).replace(/\.(vue|md)$/, "");
134
+ return join(outputDir, relative(contentBase, dirname(resolve(templatePath))), `${name}.${extension}`);
135
+ }
136
+ //#endregion
137
+ export { buildTemplate, computeContentBase, resolveOutputPath };
138
+
139
+ //# sourceMappingURL=buildTemplate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildTemplate.js","names":["parsePath"],"sources":["../../src/render/buildTemplate.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'\nimport { resolve, dirname, basename, relative, join, sep, parse as parsePath } from 'node:path'\nimport defu from 'defu'\nimport { runTransformers } from '../transformers/index.ts'\nimport { createPlaintext } from '../plaintext.ts'\nimport { stripForHtml, stripForPlaintext } from '../utils/output-markers.ts'\nimport { _setCurrentTemplate } from '../composables/useCurrentTemplate.ts'\nimport type { EventManager } from '../events/index.ts'\nimport type { Renderer } from './createRenderer.ts'\nimport type { MaizzleConfig } from '../types/index.ts'\n\nexport interface BuildTemplateContext {\n config: MaizzleConfig\n renderer: Renderer\n events: EventManager\n outputPath: string\n outputExtension: string\n contentBase: string\n}\n\nexport interface BuildTemplateResult {\n /** Output files written for this template (html + optional plaintext). */\n files: string[]\n /**\n * Number of SFC-registered `afterBuild` handlers seen while rendering. They\n * only fire once at end of build on the main thread, so a worker can't run\n * them — the count lets the orchestrator warn instead of silently dropping.\n */\n sfcAfterBuildCount: number\n}\n\n/**\n * Render a single template through the full pipeline and write its output.\n *\n * Shared by the sequential build loop and the parallel build worker so both\n * paths produce byte-identical output. `events` is the manager the per-template\n * events fire on (config handlers registered via `registerConfig`, SFC handlers\n * registered here from the render). The caller owns build-scoped events\n * (`beforeCreate`/`afterBuild`).\n */\nexport async function buildTemplate(\n templatePath: string,\n ctx: BuildTemplateContext,\n): Promise<BuildTemplateResult> {\n const { config, renderer, events, outputPath, outputExtension, contentBase } = ctx\n const absolutePath = resolve(templatePath)\n const parsedPath = parsePath(absolutePath)\n const template = { source: readFileSync(absolutePath, 'utf-8'), path: parsedPath }\n const files: string[] = []\n let sfcAfterBuildCount = 0\n\n _setCurrentTemplate(parsedPath)\n\n try {\n /**\n * Clone config per template so beforeRender mutations (setting a\n * preheader, injecting fetched data, etc.) stay scoped to this template\n * instead of leaking into later ones through the shared config object.\n */\n const renderConfig = defu({}, config) as MaizzleConfig\n const originalSource = template.source\n\n await events.fireBeforeRender({ config: renderConfig, template })\n\n const rendered = await renderer.render(\n absolutePath,\n renderConfig,\n template.source !== originalSource ? { source: template.source } : undefined,\n )\n\n /**\n * Register SFC event handlers collected during render so they take part in\n * the post-render events. Cleared at the end of this call so they don't\n * leak into the next template (afterBuild is the exception — it's never\n * cleared by clearSfcHandlers; see the count above).\n */\n for (const { name, handler } of rendered.sfcEventHandlers) {\n if (name === 'afterBuild') sfcAfterBuildCount++\n events.on(name, handler)\n }\n\n const templateConfig = rendered.templateConfig\n\n let html = await events.fireAfterRender({ config: templateConfig, template, html: rendered.html })\n\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n\n if (templateConfig.useTransformers !== false) {\n html = await runTransformers(html, templateConfig, absolutePath, doctype, rendered.tailwindBlocks)\n }\n\n html = await events.fireAfterTransform({ config: templateConfig, template, html })\n if (doctype) html = `${doctype}\\n${html}`\n\n const htmlOut = stripForHtml(html)\n const sfcOutputPath = rendered.outputPath\n let outputFilePath: string\n\n if (sfcOutputPath) {\n const parsed = parsePath(resolve(sfcOutputPath))\n const ext = parsed.ext ? parsed.ext.slice(1) : outputExtension\n outputFilePath = join(parsed.dir, `${parsed.name}.${ext}`)\n } else {\n outputFilePath = resolveOutputPath(templatePath, outputPath, outputExtension, contentBase)\n }\n\n mkdirSync(dirname(outputFilePath), { recursive: true })\n writeFileSync(outputFilePath, htmlOut)\n files.push(outputFilePath)\n\n // Generate plaintext version if configured\n const globalPlaintext = templateConfig.plaintext\n const sfcPlaintext = rendered.plaintext\n\n if (globalPlaintext || sfcPlaintext) {\n const globalCfg = typeof globalPlaintext === 'object' ? globalPlaintext : {}\n const stripOptions = defu(sfcPlaintext?.options, globalCfg.options)\n const plaintext = createPlaintext(stripForPlaintext(html), stripOptions)\n const ptExtension = sfcPlaintext?.extension ?? globalCfg.extension ?? 'txt'\n\n let ptOutputPath: string\n\n if (sfcPlaintext?.destination) {\n ptOutputPath = resolveOutputPath(templatePath, resolve(sfcPlaintext.destination), ptExtension, contentBase)\n } else if (sfcOutputPath) {\n const parsed = parsePath(outputFilePath)\n ptOutputPath = join(parsed.dir, `${parsed.name}.${ptExtension}`)\n } else if (globalCfg.destination) {\n ptOutputPath = resolveOutputPath(templatePath, resolve(globalCfg.destination), ptExtension, contentBase)\n } else {\n ptOutputPath = resolveOutputPath(templatePath, outputPath, ptExtension, contentBase)\n }\n\n mkdirSync(dirname(ptOutputPath), { recursive: true })\n writeFileSync(ptOutputPath, plaintext)\n }\n } finally {\n _setCurrentTemplate(undefined)\n events.clearSfcHandlers()\n }\n\n return { files, sfcAfterBuildCount }\n}\n\n/**\n * Extract the static (non-glob) prefix from content patterns.\n *\n * For example, `['/abs/path/emails/**\\/*.vue']` → `'/abs/path/emails'`\n *\n * Used to strip the content base from template paths so the output preserves\n * only the subdirectory structure.\n *\n * With multiple positive patterns (multi-root setups), returns their common\n * ancestor directory so templates from every root keep a clean relative path.\n */\nexport function computeContentBase(patterns: string[]): string {\n const positives = patterns.filter(p => !p.startsWith('!'))\n const sources = positives.length > 0 ? positives : patterns\n\n const bases = sources.map((pattern) => {\n // Split on first glob character (* { ? [) and take the directory part\n const staticPart = pattern.split(/[*{?[]/)[0]\n // Ensure we have a clean directory path (not a partial segment)\n return resolve(staticPart.endsWith('/') ? staticPart : dirname(staticPart))\n })\n\n return bases.reduce(commonPath)\n}\n\n/** Longest common directory path shared by two absolute paths. */\nfunction commonPath(a: string, b: string): string {\n const aSegments = a.split(sep)\n const bSegments = b.split(sep)\n const shared: string[] = []\n\n for (let i = 0; i < Math.min(aSegments.length, bSegments.length); i++) {\n if (aSegments[i] !== bSegments[i]) break\n shared.push(aSegments[i])\n }\n\n return shared.join(sep) || sep\n}\n\nexport function resolveOutputPath(templatePath: string, outputDir: string, extension: string, contentBase: string): string {\n const name = basename(templatePath).replace(/\\.(vue|md)$/, '')\n const absTemplate = resolve(templatePath)\n const rel = relative(contentBase, dirname(absTemplate))\n\n return join(outputDir, rel, `${name}.${extension}`)\n}\n"],"mappings":"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"}
@@ -15,7 +15,9 @@ interface RenderedTemplate {
15
15
  tailwindBlocks?: RenderContext['tailwindBlocks'];
16
16
  }
17
17
  interface Renderer {
18
- render(input: string | Component, config: MaizzleConfig): Promise<RenderedTemplate>;
18
+ render(input: string | Component, config: MaizzleConfig, opts?: {
19
+ source?: string;
20
+ }): Promise<RenderedTemplate>;
19
21
  invalidate(filePath: string): Promise<void>;
20
22
  invalidateAll(): Promise<void>;
21
23
  close(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"createRenderer.d.ts","names":[],"sources":["../../src/render/createRenderer.ts"],"mappings":";;;;;;;UAkCiB,gBAAA;EACf,IAAA;EACA,OAAA;EACA,cAAA,EAAgB,aAAA;EAChB,gBAAA,EAAkB,aAAA;EAClB,SAAA,GAAY,aAAA;EACZ,UAAA,GAAa,aAAA;EACb,cAAA,GAAiB,aAAA;AAAA;AAAA,UAGF,QAAA;EACf,MAAA,CAAO,KAAA,WAAgB,SAAA,EAAW,MAAA,EAAQ,aAAA,GAAgB,OAAA,CAAQ,gBAAA;EAClE,UAAA,CAAW,QAAA,WAAmB,OAAA;EAC9B,aAAA,IAAiB,OAAA;EACjB,KAAA,IAAS,OAAA;AAAA;AAAA,UAGM,qBAAA;EAdC;EAgBhB,GAAA;EAfkB;EAiBlB,QAAA,GAAW,cAAA;EAhBC;EAkBZ,IAAA;EAjBa;;;;EAsBb,aAAA,GAAgB,yBAAA;EAlBD;EAoBf,IAAA,GAAO,YAAA;AAAA;;;;;;;iBASa,cAAA,CACpB,OAAA,GAAS,qBAAA,GACR,OAAA,CAAQ,QAAA"}
1
+ {"version":3,"file":"createRenderer.d.ts","names":[],"sources":["../../src/render/createRenderer.ts"],"mappings":";;;;;;;UAmCiB,gBAAA;EACf,IAAA;EACA,OAAA;EACA,cAAA,EAAgB,aAAA;EAChB,gBAAA,EAAkB,aAAA;EAClB,SAAA,GAAY,aAAA;EACZ,UAAA,GAAa,aAAA;EACb,cAAA,GAAiB,aAAA;AAAA;AAAA,UAGF,QAAA;EACf,MAAA,CAAO,KAAA,WAAgB,SAAA,EAAW,MAAA,EAAQ,aAAA,EAAe,IAAA;IAAS,MAAA;EAAA,IAAoB,OAAA,CAAQ,gBAAA;EAC9F,UAAA,CAAW,QAAA,WAAmB,OAAA;EAC9B,aAAA,IAAiB,OAAA;EACjB,KAAA,IAAS,OAAA;AAAA;AAAA,UAGM,qBAAA;EAbG;EAelB,GAAA;EAdY;EAgBZ,QAAA,GAAW,cAAA;EAfE;EAiBb,IAAA;EAhBiB;;AAAa;AAGhC;EAkBE,aAAA,GAAgB,yBAAA;;EAEhB,IAAA,GAAO,YAAA;AAAA;;;;;;;iBASa,cAAA,CACpB,OAAA,GAAS,qBAAA,GACR,OAAA,CAAQ,QAAA"}
@@ -6,10 +6,11 @@ import { rawExtract } from "./plugins/rawExtract.js";
6
6
  import { codeBlockExtract } from "./plugins/codeBlockExtract.js";
7
7
  import { markdownExtract } from "./plugins/markdownExtract.js";
8
8
  import { componentNameFromPath } from "../utils/componentSources.js";
9
+ import { shikiToCodeBlock } from "../components/utils.js";
9
10
  import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
10
11
  import { dirname, relative, resolve } from "node:path";
11
- import { glob, globSync } from "tinyglobby";
12
12
  import { fileURLToPath } from "node:url";
13
+ import { glob, globSync } from "tinyglobby";
13
14
  import { defu as defu$1 } from "defu";
14
15
  import { createSSRApp } from "vue";
15
16
  import { createServer, mergeConfig } from "vite";
@@ -147,6 +148,14 @@ async function createRenderer(options = {}) {
147
148
  const VIRTUAL_SFC_ID = "virtual:maizzle-sfc.vue";
148
149
  let virtualSfcSource = "";
149
150
  /**
151
+ * Per-render source overrides keyed by absolute template path. Lets the
152
+ * build's beforeRender event rewrite a template's source before compile
153
+ * while keeping the real file id — so relative imports, asset URLs and
154
+ * component resolution still resolve against the actual file location
155
+ * (which the virtual-SFC path can't do).
156
+ */
157
+ const sourceOverrides = /* @__PURE__ */ new Map();
158
+ /**
150
159
  * Never load the host project's vite.config.ts here. Doing so pulls
151
160
  * every host plugin (Nitro, TanStack Start, the Maizzle plugin
152
161
  * itself, …) into this isolated SSR pipeline, where they override
@@ -170,6 +179,13 @@ async function createRenderer(options = {}) {
170
179
  if (id === VIRTUAL_SFC_ID) return virtualSfcSource;
171
180
  }
172
181
  },
182
+ {
183
+ name: "maizzle:source-override",
184
+ load(id) {
185
+ const override = sourceOverrides.get(id.split("?")[0]);
186
+ if (override !== void 0) return override;
187
+ }
188
+ },
173
189
  vue({
174
190
  include: [/\.vue$/, /\.md$/],
175
191
  template: {
@@ -203,15 +219,10 @@ isCustomElement: (tag) => tag.startsWith("amp-") }
203
219
  });
204
220
  } },
205
221
  markdownSetup(md) {
206
- const wrapPre = (html) => `<table class="w-full"><tr><td class="max-w-0 mso-padding-alt-4">${html}</td></tr></table>\n`;
207
222
  const defaultFence = md.renderer.rules.fence;
208
- md.renderer.rules.fence = (...args) => {
209
- const result = defaultFence(...args);
210
- if (typeof result === "string") return wrapPre(result);
211
- return result.then(wrapPre);
212
- };
223
+ md.renderer.rules.fence = (...args) => Promise.resolve(defaultFence(...args)).then(shikiToCodeBlock);
213
224
  const defaultCodeBlock = md.renderer.rules.code_block;
214
- md.renderer.rules.code_block = (...args) => wrapPre(defaultCodeBlock(...args));
225
+ md.renderer.rules.code_block = (...args) => shikiToCodeBlock(defaultCodeBlock(...args));
215
226
  }
216
227
  })),
217
228
  AutoImport({
@@ -290,7 +301,7 @@ isCustomElement: (tag) => tag.startsWith("amp-") }
290
301
  };
291
302
  const server = await createServer(userViteConfig ? mergeConfig(userViteConfig, maizzleConfig) : maizzleConfig);
292
303
  return {
293
- async render(input, config) {
304
+ async render(input, config, opts) {
294
305
  let component;
295
306
  let configKey;
296
307
  let contextKey;
@@ -308,7 +319,29 @@ isCustomElement: (tag) => tag.startsWith("amp-") }
308
319
  const mod = server.moduleGraph.getModuleById(VIRTUAL_SFC_ID);
309
320
  if (mod) server.moduleGraph.invalidateModule(mod);
310
321
  component = (await server.ssrLoadModule(VIRTUAL_SFC_ID)).default;
311
- } else component = (await server.ssrLoadModule(input)).default;
322
+ } else {
323
+ /**
324
+ * A beforeRender handler may have rewritten the source. Register it
325
+ * under the real path id and invalidate so ssrLoadModule compiles
326
+ * the override; clear + invalidate afterwards so the override never
327
+ * leaks into a later render of the same path.
328
+ */
329
+ const hasOverride = opts?.source !== void 0;
330
+ if (hasOverride) {
331
+ sourceOverrides.set(input, opts.source);
332
+ const mod = await server.moduleGraph.getModuleByUrl(input);
333
+ if (mod) server.moduleGraph.invalidateModule(mod);
334
+ }
335
+ try {
336
+ component = (await server.ssrLoadModule(input)).default;
337
+ } finally {
338
+ if (hasOverride) {
339
+ sourceOverrides.delete(input);
340
+ const mod = await server.moduleGraph.getModuleByUrl(input);
341
+ if (mod) server.moduleGraph.invalidateModule(mod);
342
+ }
343
+ }
344
+ }
312
345
  } else {
313
346
  component = input;
314
347
  configKey = MaizzleConfigKey;