@maizzle/framework 6.0.0-rc.13 → 6.0.0-rc.15

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 (79) hide show
  1. package/dist/components/Button.vue +2 -2
  2. package/dist/components/CodeBlock.vue +2 -1
  3. package/dist/components/Column.vue +28 -22
  4. package/dist/components/Container.vue +47 -9
  5. package/dist/components/Font.vue +96 -0
  6. package/dist/components/Head.vue +1 -1
  7. package/dist/components/Layout.vue +24 -4
  8. package/dist/components/Outlook.vue +38 -11
  9. package/dist/components/Overlap.vue +75 -18
  10. package/dist/components/Row.vue +90 -19
  11. package/dist/components/Section.vue +35 -8
  12. package/dist/components/utils.d.mts +14 -1
  13. package/dist/components/utils.d.mts.map +1 -1
  14. package/dist/components/utils.mjs +32 -1
  15. package/dist/components/utils.mjs.map +1 -1
  16. package/dist/components/utils.ts +39 -0
  17. package/dist/composables/renderContext.d.mts +8 -1
  18. package/dist/composables/renderContext.d.mts.map +1 -1
  19. package/dist/composables/renderContext.mjs.map +1 -1
  20. package/dist/composables/useFont.d.mts +50 -0
  21. package/dist/composables/useFont.d.mts.map +1 -0
  22. package/dist/composables/useFont.mjs +93 -0
  23. package/dist/composables/useFont.mjs.map +1 -0
  24. package/dist/index.d.mts +3 -1
  25. package/dist/index.mjs +3 -1
  26. package/dist/plugins/postcss/quoteFontFamilies.d.mts +13 -0
  27. package/dist/plugins/postcss/quoteFontFamilies.d.mts.map +1 -0
  28. package/dist/plugins/postcss/quoteFontFamilies.mjs +84 -0
  29. package/dist/plugins/postcss/quoteFontFamilies.mjs.map +1 -0
  30. package/dist/prepare.d.mts +17 -0
  31. package/dist/prepare.d.mts.map +1 -0
  32. package/dist/prepare.mjs +44 -0
  33. package/dist/prepare.mjs.map +1 -0
  34. package/dist/render/createRenderer.d.mts.map +1 -1
  35. package/dist/render/createRenderer.mjs +10 -2
  36. package/dist/render/createRenderer.mjs.map +1 -1
  37. package/dist/render/injectFonts.d.mts +15 -0
  38. package/dist/render/injectFonts.d.mts.map +1 -0
  39. package/dist/render/injectFonts.mjs +46 -0
  40. package/dist/render/injectFonts.mjs.map +1 -0
  41. package/dist/render/plugins/rowSourceLocation.d.mts +18 -0
  42. package/dist/render/plugins/rowSourceLocation.d.mts.map +1 -0
  43. package/dist/render/plugins/rowSourceLocation.mjs +45 -0
  44. package/dist/render/plugins/rowSourceLocation.mjs.map +1 -0
  45. package/dist/serve.d.mts.map +1 -1
  46. package/dist/serve.mjs +6 -2
  47. package/dist/serve.mjs.map +1 -1
  48. package/dist/server/ui/App.vue +25 -11
  49. package/dist/server/ui/lib/emulated-dark-mode.ts +131 -0
  50. package/dist/server/ui/pages/Preview.vue +24 -5
  51. package/dist/transformers/columnWidth.d.mts +31 -0
  52. package/dist/transformers/columnWidth.d.mts.map +1 -0
  53. package/dist/transformers/columnWidth.mjs +166 -0
  54. package/dist/transformers/columnWidth.mjs.map +1 -0
  55. package/dist/transformers/filters/index.mjs +1 -1
  56. package/dist/transformers/filters/index.mjs.map +1 -1
  57. package/dist/transformers/index.d.mts.map +1 -1
  58. package/dist/transformers/index.mjs +5 -1
  59. package/dist/transformers/index.mjs.map +1 -1
  60. package/dist/transformers/msoWidthFromClass.d.mts +19 -0
  61. package/dist/transformers/msoWidthFromClass.d.mts.map +1 -0
  62. package/dist/transformers/msoWidthFromClass.mjs +61 -0
  63. package/dist/transformers/msoWidthFromClass.mjs.map +1 -0
  64. package/dist/transformers/tailwindcss.d.mts.map +1 -1
  65. package/dist/transformers/tailwindcss.mjs +4 -12
  66. package/dist/transformers/tailwindcss.mjs.map +1 -1
  67. package/dist/utils/decodeStyleEntities.d.mts +15 -0
  68. package/dist/utils/decodeStyleEntities.d.mts.map +1 -0
  69. package/dist/utils/decodeStyleEntities.mjs +18 -0
  70. package/dist/utils/decodeStyleEntities.mjs.map +1 -0
  71. package/node_modules/maizzle/dist/commands/new.mjs +3 -3
  72. package/node_modules/maizzle/dist/index.d.mts +1 -0
  73. package/node_modules/maizzle/dist/index.mjs +3 -0
  74. package/node_modules/maizzle/package.json +3 -2
  75. package/node_modules/nypm/dist/cli.mjs +28 -5
  76. package/node_modules/nypm/dist/index.d.mts +0 -8
  77. package/node_modules/nypm/dist/index.mjs +27 -4
  78. package/node_modules/nypm/package.json +12 -12
  79. package/package.json +2 -1
@@ -0,0 +1,166 @@
1
+ import { walk } from "../utils/ast/walker.mjs";
2
+ import "../utils/ast/index.mjs";
3
+
4
+ //#region src/transformers/columnWidth.ts
5
+ const RE_MAX_WIDTH = /(?:^|;\s*)max-width:\s*([^;]+)/i;
6
+ const RE_WIDTH = /(?:^|;\s*)width:\s*([^;]+)/i;
7
+ const RE_MIN_WIDTH = /(?:^|;\s*)min-width:\s*([^;]+)/i;
8
+ const RE_MAX_HEIGHT = /(?:^|;\s*)max-height:\s*([^;]+)/i;
9
+ const RE_HEIGHT = /(?:^|;\s*)height:\s*([^;]+)/i;
10
+ const RE_MIN_HEIGHT = /(?:^|;\s*)min-height:\s*([^;]+)/i;
11
+ const RE_PERCENT = /^[\d.]+%$/;
12
+ function resolveLength(value) {
13
+ const trimmed = value.trim();
14
+ if (RE_PERCENT.test(trimmed)) return trimmed;
15
+ const m = trimmed.match(/^([\d.]+)(px|rem|em|pt)?$/i);
16
+ if (!m) return null;
17
+ const n = parseFloat(m[1]);
18
+ switch ((m[2] || "px").toLowerCase()) {
19
+ case "px": return `${Math.round(n)}px`;
20
+ case "rem":
21
+ case "em": return `${Math.round(n * 16)}px`;
22
+ case "pt": return `${Math.round(n * 1.333)}px`;
23
+ default: return null;
24
+ }
25
+ }
26
+ function divideLength(value, divisor) {
27
+ const m = value.match(/^([\d.]+)(px|%)$/);
28
+ if (!m || divisor < 1) return null;
29
+ const n = parseFloat(m[1]);
30
+ return `${parseFloat((n / divisor).toFixed(2))}${m[2]}`;
31
+ }
32
+ function depth(node) {
33
+ let d = 0;
34
+ let cur = node.parent;
35
+ while (cur) {
36
+ d++;
37
+ cur = cur.parent ?? null;
38
+ }
39
+ return d;
40
+ }
41
+ function readWidthSource(el) {
42
+ const explicit = el.attribs?.["data-maizzle-cw"];
43
+ if (explicit) {
44
+ const r = resolveLength(explicit);
45
+ if (r) return r;
46
+ }
47
+ return readWidthFromStyle(el);
48
+ }
49
+ function readWidthFromStyle(el) {
50
+ const style = el.attribs?.style ?? "";
51
+ const raw = style.match(RE_MAX_WIDTH)?.[1] ?? style.match(RE_WIDTH)?.[1] ?? style.match(RE_MIN_WIDTH)?.[1];
52
+ return raw ? resolveLength(raw) : null;
53
+ }
54
+ function readHeightFromStyle(el) {
55
+ const style = el.attribs?.style ?? "";
56
+ const raw = style.match(RE_MAX_HEIGHT)?.[1] ?? style.match(RE_HEIGHT)?.[1] ?? style.match(RE_MIN_HEIGHT)?.[1];
57
+ return raw ? resolveLength(raw) : null;
58
+ }
59
+ /**
60
+ * Resolve `__MAIZZLE_COLW_{id}__` and `__MAIZZLE_OH_{id}__` placeholders.
61
+ *
62
+ * COLW (column width) — emitted by `<Column>` and `<Overlap>`. Walks up to
63
+ * the nearest ancestor marked with `data-maizzle-cw` (Container/Section/
64
+ * Row/another Column already resolved) and divides by `data-maizzle-cw-count`.
65
+ * If `data-maizzle-cw-self` is present, reads from the element's own inlined
66
+ * `max-width`/`width`/`min-width` instead of walking up — used by `<Overlap>`
67
+ * when it has its own width class/inline style.
68
+ *
69
+ * OH (overlap height) — emitted by `<Overlap>`. Reads `max-height`/`height`/
70
+ * `min-height` from the element's own inlined style.
71
+ *
72
+ * Resolution rules:
73
+ * - Style placeholders for `min-width`: replaced when resolvable, otherwise
74
+ * the entire `min-width` declaration is stripped.
75
+ * - Other style placeholders (Overlap td `width`, etc.): replaced when
76
+ * resolvable, otherwise replaced with the count-based fallback or `100%`.
77
+ * - Comment placeholders: same fallback chain.
78
+ *
79
+ * Resolved column widths are written back to `data-maizzle-cw` so nested
80
+ * rows cascade. All `data-maizzle-cw*` and `data-maizzle-oh-*` attrs are
81
+ * stripped at the end.
82
+ */
83
+ function columnWidth(dom) {
84
+ const columns = [];
85
+ const heightTargets = [];
86
+ walk(dom, (node) => {
87
+ const el = node;
88
+ if (!el.attribs) return;
89
+ const id = el.attribs["data-maizzle-cw-id"];
90
+ if (id) {
91
+ const count = parseInt(el.attribs["data-maizzle-cw-count"] || "1", 10);
92
+ const self = "data-maizzle-cw-self" in el.attribs;
93
+ columns.push({
94
+ el,
95
+ id,
96
+ count,
97
+ d: depth(node),
98
+ self
99
+ });
100
+ }
101
+ const ohId = el.attribs["data-maizzle-oh-id"];
102
+ if (ohId) heightTargets.push({
103
+ el,
104
+ id: ohId
105
+ });
106
+ });
107
+ columns.sort((a, b) => a.d - b.d);
108
+ const widthResolutions = /* @__PURE__ */ new Map();
109
+ const widthFallbacks = /* @__PURE__ */ new Map();
110
+ for (const { id, count } of columns) widthFallbacks.set(id, `${Math.round(100 / Math.max(count, 1))}%`);
111
+ for (const { el, id, count, self } of columns) {
112
+ let sourceWidth = null;
113
+ if (self) sourceWidth = readWidthFromStyle(el);
114
+ else {
115
+ let cur = el.parent;
116
+ while (cur) {
117
+ const parentEl = cur;
118
+ if (parentEl.attribs && "data-maizzle-cw" in parentEl.attribs) {
119
+ sourceWidth = readWidthSource(parentEl);
120
+ break;
121
+ }
122
+ cur = cur.parent ?? null;
123
+ }
124
+ }
125
+ if (sourceWidth) {
126
+ const div = divideLength(sourceWidth, count);
127
+ if (div) {
128
+ widthResolutions.set(id, div);
129
+ el.attribs["data-maizzle-cw"] = div;
130
+ }
131
+ }
132
+ }
133
+ const heightResolutions = /* @__PURE__ */ new Map();
134
+ for (const { el, id } of heightTargets) {
135
+ const h = readHeightFromStyle(el);
136
+ if (h) heightResolutions.set(id, h);
137
+ }
138
+ walk(dom, (node) => {
139
+ if (node.type === "comment") {
140
+ const data = node.data;
141
+ if (!data || !data.includes("__MAIZZLE_COLW_") && !data.includes("__MAIZZLE_OH_")) return;
142
+ node.data = data.replace(/__MAIZZLE_COLW_([^_]+)__/g, (_m, mid) => widthResolutions.get(mid) ?? widthFallbacks.get(mid) ?? "100%").replace(/__MAIZZLE_OH_([^_]+)__/g, (_m, hid) => heightResolutions.get(hid) ?? "100%");
143
+ return;
144
+ }
145
+ const el = node;
146
+ if (!el.attribs) return;
147
+ const style = el.attribs.style;
148
+ if (style && (style.includes("__MAIZZLE_COLW_") || style.includes("__MAIZZLE_OH_"))) {
149
+ let next = style.replace(/(?:^|;\s*)min-width:\s*__MAIZZLE_COLW_([^_]+)__\s*;?/g, (_m, mid) => widthResolutions.has(mid) ? `; min-width: ${widthResolutions.get(mid)}` : "");
150
+ next = next.replace(/__MAIZZLE_COLW_([^_]+)__/g, (_m, mid) => widthResolutions.get(mid) ?? widthFallbacks.get(mid) ?? "100%").replace(/__MAIZZLE_OH_([^_]+)__/g, (_m, hid) => heightResolutions.get(hid) ?? "100%");
151
+ next = next.replace(/^;\s*/, "").replace(/;\s*$/, "").trim();
152
+ if (next) el.attribs.style = next;
153
+ else delete el.attribs.style;
154
+ }
155
+ delete el.attribs["data-maizzle-cw"];
156
+ delete el.attribs["data-maizzle-cw-id"];
157
+ delete el.attribs["data-maizzle-cw-count"];
158
+ delete el.attribs["data-maizzle-cw-self"];
159
+ delete el.attribs["data-maizzle-oh-id"];
160
+ });
161
+ return dom;
162
+ }
163
+
164
+ //#endregion
165
+ export { columnWidth };
166
+ //# sourceMappingURL=columnWidth.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"columnWidth.mjs","names":[],"sources":["../../src/transformers/columnWidth.ts"],"sourcesContent":["import { walk } from '../utils/ast/index.ts'\nimport type { ChildNode, Element, ParentNode } from 'domhandler'\n\nconst RE_MAX_WIDTH = /(?:^|;\\s*)max-width:\\s*([^;]+)/i\nconst RE_WIDTH = /(?:^|;\\s*)width:\\s*([^;]+)/i\nconst RE_MIN_WIDTH = /(?:^|;\\s*)min-width:\\s*([^;]+)/i\nconst RE_MAX_HEIGHT = /(?:^|;\\s*)max-height:\\s*([^;]+)/i\nconst RE_HEIGHT = /(?:^|;\\s*)height:\\s*([^;]+)/i\nconst RE_MIN_HEIGHT = /(?:^|;\\s*)min-height:\\s*([^;]+)/i\nconst RE_PERCENT = /^[\\d.]+%$/\n\nfunction resolveLength(value: string): string | null {\n const trimmed = value.trim()\n if (RE_PERCENT.test(trimmed)) return trimmed\n const m = trimmed.match(/^([\\d.]+)(px|rem|em|pt)?$/i)\n if (!m) return null\n const n = parseFloat(m[1])\n switch ((m[2] || 'px').toLowerCase()) {\n case 'px': return `${Math.round(n)}px`\n case 'rem':\n case 'em': return `${Math.round(n * 16)}px`\n case 'pt': return `${Math.round(n * 1.333)}px`\n default: return null\n }\n}\n\nfunction divideLength(value: string, divisor: number): string | null {\n const m = value.match(/^([\\d.]+)(px|%)$/)\n if (!m || divisor < 1) return null\n const n = parseFloat(m[1])\n return `${parseFloat((n / divisor).toFixed(2))}${m[2]}`\n}\n\nfunction depth(node: ChildNode): number {\n let d = 0\n let cur: ParentNode | null = node.parent\n while (cur) {\n d++\n cur = (cur as any).parent ?? null\n }\n return d\n}\n\nfunction readWidthSource(el: Element): string | null {\n const explicit = el.attribs?.['data-maizzle-cw']\n if (explicit) {\n const r = resolveLength(explicit)\n if (r) return r\n }\n return readWidthFromStyle(el)\n}\n\nfunction readWidthFromStyle(el: Element): string | null {\n const style = el.attribs?.style ?? ''\n const raw = style.match(RE_MAX_WIDTH)?.[1]\n ?? style.match(RE_WIDTH)?.[1]\n ?? style.match(RE_MIN_WIDTH)?.[1]\n return raw ? resolveLength(raw) : null\n}\n\nfunction readHeightFromStyle(el: Element): string | null {\n const style = el.attribs?.style ?? ''\n const raw = style.match(RE_MAX_HEIGHT)?.[1]\n ?? style.match(RE_HEIGHT)?.[1]\n ?? style.match(RE_MIN_HEIGHT)?.[1]\n return raw ? resolveLength(raw) : null\n}\n\n/**\n * Resolve `__MAIZZLE_COLW_{id}__` and `__MAIZZLE_OH_{id}__` placeholders.\n *\n * COLW (column width) — emitted by `<Column>` and `<Overlap>`. Walks up to\n * the nearest ancestor marked with `data-maizzle-cw` (Container/Section/\n * Row/another Column already resolved) and divides by `data-maizzle-cw-count`.\n * If `data-maizzle-cw-self` is present, reads from the element's own inlined\n * `max-width`/`width`/`min-width` instead of walking up — used by `<Overlap>`\n * when it has its own width class/inline style.\n *\n * OH (overlap height) — emitted by `<Overlap>`. Reads `max-height`/`height`/\n * `min-height` from the element's own inlined style.\n *\n * Resolution rules:\n * - Style placeholders for `min-width`: replaced when resolvable, otherwise\n * the entire `min-width` declaration is stripped.\n * - Other style placeholders (Overlap td `width`, etc.): replaced when\n * resolvable, otherwise replaced with the count-based fallback or `100%`.\n * - Comment placeholders: same fallback chain.\n *\n * Resolved column widths are written back to `data-maizzle-cw` so nested\n * rows cascade. All `data-maizzle-cw*` and `data-maizzle-oh-*` attrs are\n * stripped at the end.\n */\nexport function columnWidth(dom: ChildNode[]): ChildNode[] {\n const columns: { el: Element; id: string; count: number; d: number; self: boolean }[] = []\n const heightTargets: { el: Element; id: string }[] = []\n\n walk(dom, (node) => {\n const el = node as Element\n if (!el.attribs) return\n\n const id = el.attribs['data-maizzle-cw-id']\n if (id) {\n const count = parseInt(el.attribs['data-maizzle-cw-count'] || '1', 10)\n const self = 'data-maizzle-cw-self' in el.attribs\n columns.push({ el, id, count, d: depth(node), self })\n }\n\n const ohId = el.attribs['data-maizzle-oh-id']\n if (ohId) heightTargets.push({ el, id: ohId })\n })\n\n columns.sort((a, b) => a.d - b.d)\n\n const widthResolutions = new Map<string, string>()\n const widthFallbacks = new Map<string, string>()\n\n for (const { id, count } of columns) {\n widthFallbacks.set(id, `${Math.round(100 / Math.max(count, 1))}%`)\n }\n\n for (const { el, id, count, self } of columns) {\n let sourceWidth: string | null = null\n\n if (self) {\n sourceWidth = readWidthFromStyle(el)\n } else {\n let cur: ParentNode | null = el.parent\n while (cur) {\n const parentEl = cur as Element\n if (parentEl.attribs && 'data-maizzle-cw' in parentEl.attribs) {\n sourceWidth = readWidthSource(parentEl)\n break\n }\n cur = (cur as any).parent ?? null\n }\n }\n\n if (sourceWidth) {\n const div = divideLength(sourceWidth, count)\n if (div) {\n widthResolutions.set(id, div)\n el.attribs['data-maizzle-cw'] = div\n }\n }\n }\n\n const heightResolutions = new Map<string, string>()\n for (const { el, id } of heightTargets) {\n const h = readHeightFromStyle(el)\n if (h) heightResolutions.set(id, h)\n }\n\n walk(dom, (node) => {\n if (node.type === 'comment') {\n const data = (node as any).data as string\n if (!data || (!data.includes('__MAIZZLE_COLW_') && !data.includes('__MAIZZLE_OH_'))) return\n ;(node as any).data = data\n .replace(/__MAIZZLE_COLW_([^_]+)__/g,\n (_m, mid) => widthResolutions.get(mid) ?? widthFallbacks.get(mid) ?? '100%')\n .replace(/__MAIZZLE_OH_([^_]+)__/g,\n (_m, hid) => heightResolutions.get(hid) ?? '100%')\n return\n }\n\n const el = node as Element\n if (!el.attribs) return\n\n const style = el.attribs.style\n if (style && (style.includes('__MAIZZLE_COLW_') || style.includes('__MAIZZLE_OH_'))) {\n let next = style.replace(\n /(?:^|;\\s*)min-width:\\s*__MAIZZLE_COLW_([^_]+)__\\s*;?/g,\n (_m, mid) => widthResolutions.has(mid) ? `; min-width: ${widthResolutions.get(mid)}` : ''\n )\n next = next\n .replace(/__MAIZZLE_COLW_([^_]+)__/g,\n (_m, mid) => widthResolutions.get(mid) ?? widthFallbacks.get(mid) ?? '100%')\n .replace(/__MAIZZLE_OH_([^_]+)__/g,\n (_m, hid) => heightResolutions.get(hid) ?? '100%')\n next = next.replace(/^;\\s*/, '').replace(/;\\s*$/, '').trim()\n if (next) el.attribs.style = next\n else delete el.attribs.style\n }\n\n delete el.attribs['data-maizzle-cw']\n delete el.attribs['data-maizzle-cw-id']\n delete el.attribs['data-maizzle-cw-count']\n delete el.attribs['data-maizzle-cw-self']\n delete el.attribs['data-maizzle-oh-id']\n })\n\n return dom\n}\n"],"mappings":"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"}
@@ -9,7 +9,7 @@ import { Text } from "domhandler";
9
9
  * Process children before parents so nested filter elements work correctly.
10
10
  */
11
11
  function walkBottomUp(nodes, callback) {
12
- for (const node of [...nodes]) {
12
+ for (const node of nodes.slice()) {
13
13
  if ("children" in node && node.children?.length) walkBottomUp(node.children, callback);
14
14
  callback(node);
15
15
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/transformers/filters/index.ts"],"sourcesContent":["import { Text } from 'domhandler'\nimport type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize } from '../../utils/ast/index.ts'\nimport { defaults } from './defaults.ts'\n\nexport type { FilterFunction } from './defaults.ts'\nexport type FiltersConfig = false | Record<string, (str: string, value: string) => string>\n\n/**\n * Process children before parents so nested filter elements work correctly.\n */\nfunction walkBottomUp(nodes: ChildNode[], callback: (node: ChildNode) => void): void {\n for (const node of [...nodes]) {\n if ('children' in node && node.children?.length) {\n walkBottomUp(node.children as ChildNode[], callback)\n }\n\n callback(node)\n }\n}\n\n/**\n * Filters transformer.\n *\n * Applies transformation functions to the content of elements that\n * have matching filter attributes. Multiple filters on the same element\n * are executed in the order the attributes are defined.\n *\n * Default filters include string manipulation (uppercase, lowercase, trim, etc.),\n * math operations (plus, minus, multiply, etc.), and more.\n *\n * Custom filters can be added via config, and will be merged with defaults.\n * Set config to `false` to disable all filters.\n */\nexport function filters(dom: ChildNode[], config: FiltersConfig = {}): ChildNode[] {\n if (config === false) return dom\n\n const allFilters = { ...defaults, ...config }\n const filterNames = new Set(Object.keys(allFilters))\n\n walkBottomUp(dom, (node) => {\n const el = node as Element\n\n if (!el.attribs) return\n\n // Collect matching filter attributes in source order\n const matched: Array<{ name: string; value: string }> = []\n\n for (const attr of Object.keys(el.attribs)) {\n if (filterNames.has(attr)) {\n matched.push({ name: attr, value: el.attribs[attr] })\n }\n }\n\n if (matched.length === 0) return\n\n // Serialize children to get innerHTML\n let content = serialize(el.children as ChildNode[])\n\n // Apply each filter in attribute order\n for (const { name, value } of matched) {\n content = allFilters[name](content, value)\n delete el.attribs[name]\n }\n\n // Replace children with the filtered content\n if (content === '') {\n el.children = []\n } else if (/<[a-z/!]/i.test(content)) {\n // Result contains HTML elements — parse back to DOM\n const newChildren = parse(content)\n\n for (const child of newChildren) {\n child.parent = el as any\n }\n\n el.children = newChildren as ChildNode[]\n } else {\n // Text-only result — create a text node directly to preserve entity strings\n const textNode = new Text(content)\n textNode.parent = el as any\n el.children = [textNode]\n }\n })\n\n return dom\n}\n"],"mappings":";;;;;;;;;;AAWA,SAAS,aAAa,OAAoB,UAA2C;AACnF,MAAK,MAAM,QAAQ,CAAC,GAAG,MAAM,EAAE;AAC7B,MAAI,cAAc,QAAQ,KAAK,UAAU,OACvC,cAAa,KAAK,UAAyB,SAAS;AAGtD,WAAS,KAAK;;;;;;;;;;;;;;;;AAiBlB,SAAgB,QAAQ,KAAkB,SAAwB,EAAE,EAAe;AACjF,KAAI,WAAW,MAAO,QAAO;CAE7B,MAAM,aAAa;EAAE,GAAG;EAAU,GAAG;EAAQ;CAC7C,MAAM,cAAc,IAAI,IAAI,OAAO,KAAK,WAAW,CAAC;AAEpD,cAAa,MAAM,SAAS;EAC1B,MAAM,KAAK;AAEX,MAAI,CAAC,GAAG,QAAS;EAGjB,MAAM,UAAkD,EAAE;AAE1D,OAAK,MAAM,QAAQ,OAAO,KAAK,GAAG,QAAQ,CACxC,KAAI,YAAY,IAAI,KAAK,CACvB,SAAQ,KAAK;GAAE,MAAM;GAAM,OAAO,GAAG,QAAQ;GAAO,CAAC;AAIzD,MAAI,QAAQ,WAAW,EAAG;EAG1B,IAAI,UAAU,UAAU,GAAG,SAAwB;AAGnD,OAAK,MAAM,EAAE,MAAM,WAAW,SAAS;AACrC,aAAU,WAAW,MAAM,SAAS,MAAM;AAC1C,UAAO,GAAG,QAAQ;;AAIpB,MAAI,YAAY,GACd,IAAG,WAAW,EAAE;WACP,YAAY,KAAK,QAAQ,EAAE;GAEpC,MAAM,cAAc,MAAM,QAAQ;AAElC,QAAK,MAAM,SAAS,YAClB,OAAM,SAAS;AAGjB,MAAG,WAAW;SACT;GAEL,MAAM,WAAW,IAAI,KAAK,QAAQ;AAClC,YAAS,SAAS;AAClB,MAAG,WAAW,CAAC,SAAS;;GAE1B;AAEF,QAAO"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/transformers/filters/index.ts"],"sourcesContent":["import { Text } from 'domhandler'\nimport type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize } from '../../utils/ast/index.ts'\nimport { defaults } from './defaults.ts'\n\nexport type { FilterFunction } from './defaults.ts'\nexport type FiltersConfig = false | Record<string, (str: string, value: string) => string>\n\n/**\n * Process children before parents so nested filter elements work correctly.\n */\nfunction walkBottomUp(nodes: ChildNode[], callback: (node: ChildNode) => void): void {\n for (const node of nodes.slice()) {\n if ('children' in node && node.children?.length) {\n walkBottomUp(node.children as ChildNode[], callback)\n }\n\n callback(node)\n }\n}\n\n/**\n * Filters transformer.\n *\n * Applies transformation functions to the content of elements that\n * have matching filter attributes. Multiple filters on the same element\n * are executed in the order the attributes are defined.\n *\n * Default filters include string manipulation (uppercase, lowercase, trim, etc.),\n * math operations (plus, minus, multiply, etc.), and more.\n *\n * Custom filters can be added via config, and will be merged with defaults.\n * Set config to `false` to disable all filters.\n */\nexport function filters(dom: ChildNode[], config: FiltersConfig = {}): ChildNode[] {\n if (config === false) return dom\n\n const allFilters = { ...defaults, ...config }\n const filterNames = new Set(Object.keys(allFilters))\n\n walkBottomUp(dom, (node) => {\n const el = node as Element\n\n if (!el.attribs) return\n\n // Collect matching filter attributes in source order\n const matched: Array<{ name: string; value: string }> = []\n\n for (const attr of Object.keys(el.attribs)) {\n if (filterNames.has(attr)) {\n matched.push({ name: attr, value: el.attribs[attr] })\n }\n }\n\n if (matched.length === 0) return\n\n // Serialize children to get innerHTML\n let content = serialize(el.children as ChildNode[])\n\n // Apply each filter in attribute order\n for (const { name, value } of matched) {\n content = allFilters[name](content, value)\n delete el.attribs[name]\n }\n\n // Replace children with the filtered content\n if (content === '') {\n el.children = []\n } else if (/<[a-z/!]/i.test(content)) {\n // Result contains HTML elements — parse back to DOM\n const newChildren = parse(content)\n\n for (const child of newChildren) {\n child.parent = el as any\n }\n\n el.children = newChildren as ChildNode[]\n } else {\n // Text-only result — create a text node directly to preserve entity strings\n const textNode = new Text(content)\n textNode.parent = el as any\n el.children = [textNode]\n }\n })\n\n return dom\n}\n"],"mappings":";;;;;;;;;;AAWA,SAAS,aAAa,OAAoB,UAA2C;AACnF,MAAK,MAAM,QAAQ,MAAM,OAAO,EAAE;AAChC,MAAI,cAAc,QAAQ,KAAK,UAAU,OACvC,cAAa,KAAK,UAAyB,SAAS;AAGtD,WAAS,KAAK;;;;;;;;;;;;;;;;AAiBlB,SAAgB,QAAQ,KAAkB,SAAwB,EAAE,EAAe;AACjF,KAAI,WAAW,MAAO,QAAO;CAE7B,MAAM,aAAa;EAAE,GAAG;EAAU,GAAG;EAAQ;CAC7C,MAAM,cAAc,IAAI,IAAI,OAAO,KAAK,WAAW,CAAC;AAEpD,cAAa,MAAM,SAAS;EAC1B,MAAM,KAAK;AAEX,MAAI,CAAC,GAAG,QAAS;EAGjB,MAAM,UAAkD,EAAE;AAE1D,OAAK,MAAM,QAAQ,OAAO,KAAK,GAAG,QAAQ,CACxC,KAAI,YAAY,IAAI,KAAK,CACvB,SAAQ,KAAK;GAAE,MAAM;GAAM,OAAO,GAAG,QAAQ;GAAO,CAAC;AAIzD,MAAI,QAAQ,WAAW,EAAG;EAG1B,IAAI,UAAU,UAAU,GAAG,SAAwB;AAGnD,OAAK,MAAM,EAAE,MAAM,WAAW,SAAS;AACrC,aAAU,WAAW,MAAM,SAAS,MAAM;AAC1C,UAAO,GAAG,QAAQ;;AAIpB,MAAI,YAAY,GACd,IAAG,WAAW,EAAE;WACP,YAAY,KAAK,QAAQ,EAAE;GAEpC,MAAM,cAAc,MAAM,QAAQ;AAElC,QAAK,MAAM,SAAS,YAClB,OAAM,SAAS;AAGjB,MAAG,WAAW;SACT;GAEL,MAAM,WAAW,IAAI,KAAK,QAAQ;AAClC,YAAS,SAAS;AAClB,MAAG,WAAW,CAAC,SAAS;;GAE1B;AAEF,QAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/transformers/index.ts"],"mappings":";;;;;AAkDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAsB,eAAA,CACpB,IAAA,UACA,MAAA,EAAQ,aAAA,EACR,QAAA,WACA,OAAA,YACC,OAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/transformers/index.ts"],"mappings":";;;;;AAoDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAsB,eAAA,CACpB,IAAA,UACA,MAAA,EAAQ,aAAA,EACR,QAAA,WACA,OAAA,YACC,OAAA"}
@@ -6,6 +6,8 @@ import { tailwindcss } from "./tailwindcss.mjs";
6
6
  import { safeClassNames } from "./safeClassNames.mjs";
7
7
  import { attributeToStyle } from "./attributeToStyle.mjs";
8
8
  import { inlineCSS } from "./inlineCSS.mjs";
9
+ import { msoWidthFromClass } from "./msoWidthFromClass.mjs";
10
+ import { columnWidth } from "./columnWidth.mjs";
9
11
  import { removeAttributes } from "./removeAttributes.mjs";
10
12
  import { shorthandCSS } from "./shorthandCSS.mjs";
11
13
  import { sixHex } from "./sixHex.mjs";
@@ -51,12 +53,15 @@ import { minify } from "./minify.mjs";
51
53
  * 16. Minify
52
54
  */
53
55
  async function runTransformers(html, config, filePath, doctype) {
56
+ html = html.replaceAll("<!--[-->", "").replaceAll("<!--]-->", "").replaceAll("<!--teleport start anchor-->", "").replaceAll("<!--teleport anchor-->", "").replaceAll("<!--teleport start-->", "").replaceAll("<!--teleport end-->", "");
54
57
  let dom = parse(html);
55
58
  dom = await inlineLink(dom, filePath);
56
59
  dom = await tailwindcss(dom, config, filePath);
57
60
  dom = safeClassNames(dom, config.css);
58
61
  dom = attributeToStyle(dom, config.css);
59
62
  dom = inlineCSS(dom, config.css);
63
+ dom = msoWidthFromClass(dom);
64
+ dom = columnWidth(dom);
60
65
  dom = removeAttributes(dom, config.html?.attributes);
61
66
  dom = shorthandCSS(dom, config.css);
62
67
  dom = sixHex(dom, config.css);
@@ -68,7 +73,6 @@ async function runTransformers(html, config, filePath, doctype) {
68
73
  dom = entities(dom, config.html?.decodeEntities);
69
74
  const isXhtml = doctype ? /xhtml/i.test(doctype) : false;
70
75
  let result = serialize(dom, { selfClosingTags: isXhtml });
71
- result = result.replaceAll("<!--[-->", "").replaceAll("<!--]-->", "").replaceAll("<!--teleport start anchor-->", "").replaceAll("<!--teleport anchor-->", "").replaceAll("<!--teleport start-->", "").replaceAll("<!--teleport end-->", "");
72
76
  result = replaceStrings(result, config);
73
77
  result = await format(result, config);
74
78
  result = minify(result, config);
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/transformers/index.ts"],"sourcesContent":["import { parse, serialize } from '../utils/ast/index.ts'\nimport { inlineLink } from './inlineLink.ts'\nimport { tailwindcss } from './tailwindcss.ts'\nimport { safeClassNames } from './safeClassNames.ts'\nimport { attributeToStyle } from './attributeToStyle.ts'\nimport { inlineCSS } from './inlineCSS.ts'\nimport { removeAttributes } from './removeAttributes.ts'\nimport { shorthandCSS } from './shorthandCSS.ts'\nimport { sixHex } from './sixHex.ts'\nimport { addAttributes } from './addAttributes.ts'\nimport { filters } from './filters/index.ts'\nimport { base } from './base.ts'\nimport { entities } from './entities.ts'\nimport { urlQuery } from './urlQuery.ts'\nimport { purgeCSS } from './purgeCSS.ts'\nimport { replaceStrings } from './replaceStrings.ts'\nimport { format } from './format.ts'\nimport { minify } from './minify.ts'\nimport type { MaizzleConfig } from '../types/config.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 * 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): Promise<string> {\n // Parse once — all DOM transformers share this array\n let dom = parse(html)\n\n // 0. Inline <link> stylesheets\n dom = await inlineLink(dom, filePath)\n\n // 1. Tailwind CSS — always runs first\n dom = await tailwindcss(dom, config, filePath)\n\n // 2. Safe class names\n dom = safeClassNames(dom, config.css)\n\n // 3. Attribute to style\n dom = attributeToStyle(dom, config.css)\n\n // 4. CSS inliner (serializes/parses internally around juice)\n dom = inlineCSS(dom, config.css)\n\n // 5. Remove attributes\n dom = removeAttributes(dom, config.html?.attributes)\n\n // 6. Shorthand CSS\n dom = shorthandCSS(dom, config.css)\n\n // 7. Six-digit HEX\n dom = sixHex(dom, config.css)\n\n // 8. Add attributes\n dom = addAttributes(dom, config.html?.attributes)\n\n // 9. Filters\n dom = filters(dom, config.filters)\n\n // 10. Base URL (serializes/parses internally for VML/MSO regex passes)\n dom = base(dom, config.url)\n\n // 11. URL query\n dom = urlQuery(dom, config.url)\n\n // 12. Purge CSS (serializes/parses internally around email-comb)\n dom = purgeCSS(dom, config.css)\n\n // 13. Entities\n dom = entities(dom, config.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 // Remove Vue-generated comments after serializing\n result = result\n .replaceAll('<!--[-->', '')\n .replaceAll('<!--]-->', '')\n .replaceAll('<!--teleport start anchor-->', '')\n .replaceAll('<!--teleport anchor-->', '')\n .replaceAll('<!--teleport start-->', '')\n .replaceAll('<!--teleport end-->', '')\n\n // 14. Replace strings\n result = replaceStrings(result, config)\n\n // 15. Format\n result = await format(result, config)\n\n // 16. Minify\n result = minify(result, config)\n\n // Strip self-closing slashes for HTML5 doctypes\n if (!isXhtml) {\n result = result.replace(/ \\/>/g, '>')\n }\n\n return result\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,eAAsB,gBACpB,MACA,QACA,UACA,SACiB;CAEjB,IAAI,MAAM,MAAM,KAAK;AAGrB,OAAM,MAAM,WAAW,KAAK,SAAS;AAGrC,OAAM,MAAM,YAAY,KAAK,QAAQ,SAAS;AAG9C,OAAM,eAAe,KAAK,OAAO,IAAI;AAGrC,OAAM,iBAAiB,KAAK,OAAO,IAAI;AAGvC,OAAM,UAAU,KAAK,OAAO,IAAI;AAGhC,OAAM,iBAAiB,KAAK,OAAO,MAAM,WAAW;AAGpD,OAAM,aAAa,KAAK,OAAO,IAAI;AAGnC,OAAM,OAAO,KAAK,OAAO,IAAI;AAG7B,OAAM,cAAc,KAAK,OAAO,MAAM,WAAW;AAGjD,OAAM,QAAQ,KAAK,OAAO,QAAQ;AAGlC,OAAM,KAAK,KAAK,OAAO,IAAI;AAG3B,OAAM,SAAS,KAAK,OAAO,IAAI;AAG/B,OAAM,SAAS,KAAK,OAAO,IAAI;AAG/B,OAAM,SAAS,KAAK,OAAO,MAAM,eAAe;CAGhD,MAAM,UAAU,UAAU,SAAS,KAAK,QAAQ,GAAG;CACnD,IAAI,SAAS,UAAU,KAAK,EAAE,iBAAiB,SAAS,CAAC;AAGzD,UAAS,OACN,WAAW,YAAY,GAAG,CAC1B,WAAW,YAAY,GAAG,CAC1B,WAAW,gCAAgC,GAAG,CAC9C,WAAW,0BAA0B,GAAG,CACxC,WAAW,yBAAyB,GAAG,CACvC,WAAW,uBAAuB,GAAG;AAGxC,UAAS,eAAe,QAAQ,OAAO;AAGvC,UAAS,MAAM,OAAO,QAAQ,OAAO;AAGrC,UAAS,OAAO,QAAQ,OAAO;AAG/B,KAAI,CAAC,QACH,UAAS,OAAO,QAAQ,SAAS,IAAI;AAGvC,QAAO"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/transformers/index.ts"],"sourcesContent":["import { parse, serialize } from '../utils/ast/index.ts'\nimport { inlineLink } from './inlineLink.ts'\nimport { tailwindcss } from './tailwindcss.ts'\nimport { safeClassNames } from './safeClassNames.ts'\nimport { attributeToStyle } from './attributeToStyle.ts'\nimport { inlineCSS } from './inlineCSS.ts'\nimport { msoWidthFromClass } from './msoWidthFromClass.ts'\nimport { columnWidth } from './columnWidth.ts'\nimport { removeAttributes } from './removeAttributes.ts'\nimport { shorthandCSS } from './shorthandCSS.ts'\nimport { sixHex } from './sixHex.ts'\nimport { addAttributes } from './addAttributes.ts'\nimport { filters } from './filters/index.ts'\nimport { base } from './base.ts'\nimport { entities } from './entities.ts'\nimport { urlQuery } from './urlQuery.ts'\nimport { purgeCSS } from './purgeCSS.ts'\nimport { replaceStrings } from './replaceStrings.ts'\nimport { format } from './format.ts'\nimport { minify } from './minify.ts'\nimport type { MaizzleConfig } from '../types/config.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 * 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): Promise<string> {\n // Strip Vue SSR fragment markers before parsing. They contain `-->`, which\n // prematurely terminates conditional comments like `<!--[if mso]>...<![endif]-->`\n // when htmlparser2 reads them, swallowing real markup into comment data.\n html = html\n .replaceAll('<!--[-->', '')\n .replaceAll('<!--]-->', '')\n .replaceAll('<!--teleport start anchor-->', '')\n .replaceAll('<!--teleport anchor-->', '')\n .replaceAll('<!--teleport start-->', '')\n .replaceAll('<!--teleport end-->', '')\n\n // Parse once — all DOM transformers share this array\n let dom = parse(html)\n\n // 0. Inline <link> stylesheets\n dom = await inlineLink(dom, filePath)\n\n // 1. Tailwind CSS — always runs first\n dom = await tailwindcss(dom, config, filePath)\n\n // 2. Safe class names\n dom = safeClassNames(dom, config.css)\n\n // 3. Attribute to style\n dom = attributeToStyle(dom, config.css)\n\n // 4. CSS inliner (serializes/parses internally around juice)\n dom = inlineCSS(dom, config.css)\n\n // 4.5. Resolve MSO width placeholders from inlined max-width/width\n dom = msoWidthFromClass(dom)\n\n // 4.6. Resolve Column min-width placeholders from nearest sized ancestor\n dom = columnWidth(dom)\n\n // 5. Remove attributes\n dom = removeAttributes(dom, config.html?.attributes)\n\n // 6. Shorthand CSS\n dom = shorthandCSS(dom, config.css)\n\n // 7. Six-digit HEX\n dom = sixHex(dom, config.css)\n\n // 8. Add attributes\n dom = addAttributes(dom, config.html?.attributes)\n\n // 9. Filters\n dom = filters(dom, config.filters)\n\n // 10. Base URL (serializes/parses internally for VML/MSO regex passes)\n dom = base(dom, config.url)\n\n // 11. URL query\n dom = urlQuery(dom, config.url)\n\n // 12. Purge CSS (serializes/parses internally around email-comb)\n dom = purgeCSS(dom, config.css)\n\n // 13. Entities\n dom = entities(dom, config.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 result = replaceStrings(result, config)\n\n // 15. Format\n result = await format(result, config)\n\n // 16. Minify\n result = minify(result, config)\n\n // Strip self-closing slashes for HTML5 doctypes\n if (!isXhtml) {\n result = result.replace(/ \\/>/g, '>')\n }\n\n return result\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,eAAsB,gBACpB,MACA,QACA,UACA,SACiB;AAIjB,QAAO,KACJ,WAAW,YAAY,GAAG,CAC1B,WAAW,YAAY,GAAG,CAC1B,WAAW,gCAAgC,GAAG,CAC9C,WAAW,0BAA0B,GAAG,CACxC,WAAW,yBAAyB,GAAG,CACvC,WAAW,uBAAuB,GAAG;CAGxC,IAAI,MAAM,MAAM,KAAK;AAGrB,OAAM,MAAM,WAAW,KAAK,SAAS;AAGrC,OAAM,MAAM,YAAY,KAAK,QAAQ,SAAS;AAG9C,OAAM,eAAe,KAAK,OAAO,IAAI;AAGrC,OAAM,iBAAiB,KAAK,OAAO,IAAI;AAGvC,OAAM,UAAU,KAAK,OAAO,IAAI;AAGhC,OAAM,kBAAkB,IAAI;AAG5B,OAAM,YAAY,IAAI;AAGtB,OAAM,iBAAiB,KAAK,OAAO,MAAM,WAAW;AAGpD,OAAM,aAAa,KAAK,OAAO,IAAI;AAGnC,OAAM,OAAO,KAAK,OAAO,IAAI;AAG7B,OAAM,cAAc,KAAK,OAAO,MAAM,WAAW;AAGjD,OAAM,QAAQ,KAAK,OAAO,QAAQ;AAGlC,OAAM,KAAK,KAAK,OAAO,IAAI;AAG3B,OAAM,SAAS,KAAK,OAAO,IAAI;AAG/B,OAAM,SAAS,KAAK,OAAO,IAAI;AAG/B,OAAM,SAAS,KAAK,OAAO,MAAM,eAAe;CAGhD,MAAM,UAAU,UAAU,SAAS,KAAK,QAAQ,GAAG;CACnD,IAAI,SAAS,UAAU,KAAK,EAAE,iBAAiB,SAAS,CAAC;AAGzD,UAAS,eAAe,QAAQ,OAAO;AAGvC,UAAS,MAAM,OAAO,QAAQ,OAAO;AAGrC,UAAS,OAAO,QAAQ,OAAO;AAG/B,KAAI,CAAC,QACH,UAAS,OAAO,QAAQ,SAAS,IAAI;AAGvC,QAAO"}
@@ -0,0 +1,19 @@
1
+ import { ChildNode } from "domhandler";
2
+
3
+ //#region src/transformers/msoWidthFromClass.d.ts
4
+ /**
5
+ * Resolve `__MAIZZLE_MSOW_{id}__` placeholders inside MSO conditional
6
+ * comments by reading the inlined `max-width` (or `width`) of the
7
+ * paired element marked with `data-maizzle-msow-id`.
8
+ *
9
+ * Used by `<Container>` and `<Section>` to derive Outlook's table width
10
+ * from the resolved Tailwind class or inline style on the inner div,
11
+ * after CSS inlining.
12
+ *
13
+ * Falls back to the value of `data-maizzle-msow-fallback` (default
14
+ * `600px`) when the value can't be parsed.
15
+ */
16
+ declare function msoWidthFromClass(dom: ChildNode[]): ChildNode[];
17
+ //#endregion
18
+ export { msoWidthFromClass };
19
+ //# sourceMappingURL=msoWidthFromClass.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"msoWidthFromClass.d.mts","names":[],"sources":["../../src/transformers/msoWidthFromClass.ts"],"mappings":";;;;;AAkCA;;;;;;;;;;iBAAgB,iBAAA,CAAkB,GAAA,EAAK,SAAA,KAAc,SAAA"}
@@ -0,0 +1,61 @@
1
+ import { walk } from "../utils/ast/walker.mjs";
2
+ import "../utils/ast/index.mjs";
3
+
4
+ //#region src/transformers/msoWidthFromClass.ts
5
+ const RE_MAX_WIDTH = /(?:^|;\s*)max-width:\s*([^;]+)/i;
6
+ const RE_WIDTH = /(?:^|;\s*)width:\s*([^;]+)/i;
7
+ const RE_PERCENT = /^[\d.]+%$/;
8
+ function resolveWidth(value) {
9
+ const trimmed = value.trim();
10
+ if (RE_PERCENT.test(trimmed)) return trimmed;
11
+ const m = trimmed.match(/^([\d.]+)(px|rem|em|pt)?$/i);
12
+ if (!m) return null;
13
+ const n = parseFloat(m[1]);
14
+ switch ((m[2] || "px").toLowerCase()) {
15
+ case "px": return `${Math.round(n)}px`;
16
+ case "rem":
17
+ case "em": return `${Math.round(n * 16)}px`;
18
+ case "pt": return `${Math.round(n * 1.333)}px`;
19
+ default: return null;
20
+ }
21
+ }
22
+ /**
23
+ * Resolve `__MAIZZLE_MSOW_{id}__` placeholders inside MSO conditional
24
+ * comments by reading the inlined `max-width` (or `width`) of the
25
+ * paired element marked with `data-maizzle-msow-id`.
26
+ *
27
+ * Used by `<Container>` and `<Section>` to derive Outlook's table width
28
+ * from the resolved Tailwind class or inline style on the inner div,
29
+ * after CSS inlining.
30
+ *
31
+ * Falls back to the value of `data-maizzle-msow-fallback` (default
32
+ * `600px`) when the value can't be parsed.
33
+ */
34
+ function msoWidthFromClass(dom) {
35
+ const widths = /* @__PURE__ */ new Map();
36
+ walk(dom, (node) => {
37
+ const el = node;
38
+ const id = el.attribs?.["data-maizzle-msow-id"];
39
+ if (!id) return;
40
+ delete el.attribs["data-maizzle-msow-id"];
41
+ const fallback = el.attribs["data-maizzle-msow-fallback"] ?? "600px";
42
+ delete el.attribs["data-maizzle-msow-fallback"];
43
+ const style = el.attribs.style ?? "";
44
+ const raw = style.match(RE_MAX_WIDTH)?.[1] ?? style.match(RE_WIDTH)?.[1];
45
+ const resolved = raw ? resolveWidth(raw) : null;
46
+ widths.set(id, resolved ?? fallback);
47
+ });
48
+ if (widths.size === 0) return dom;
49
+ walk(dom, (node) => {
50
+ if (node.type !== "comment") return;
51
+ let data = node.data;
52
+ if (!data.includes("__MAIZZLE_MSOW_")) return;
53
+ for (const [id, px] of widths) data = data.replaceAll(`__MAIZZLE_MSOW_${id}__`, px);
54
+ node.data = data;
55
+ });
56
+ return dom;
57
+ }
58
+
59
+ //#endregion
60
+ export { msoWidthFromClass };
61
+ //# sourceMappingURL=msoWidthFromClass.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"msoWidthFromClass.mjs","names":[],"sources":["../../src/transformers/msoWidthFromClass.ts"],"sourcesContent":["import { walk } from '../utils/ast/index.ts'\nimport type { ChildNode, Element } from 'domhandler'\n\nconst RE_MAX_WIDTH = /(?:^|;\\s*)max-width:\\s*([^;]+)/i\nconst RE_WIDTH = /(?:^|;\\s*)width:\\s*([^;]+)/i\nconst RE_PERCENT = /^[\\d.]+%$/\n\nfunction resolveWidth(value: string): string | null {\n const trimmed = value.trim()\n if (RE_PERCENT.test(trimmed)) return trimmed\n const m = trimmed.match(/^([\\d.]+)(px|rem|em|pt)?$/i)\n if (!m) return null\n const n = parseFloat(m[1])\n switch ((m[2] || 'px').toLowerCase()) {\n case 'px': return `${Math.round(n)}px`\n case 'rem':\n case 'em': return `${Math.round(n * 16)}px`\n case 'pt': return `${Math.round(n * 1.333)}px`\n default: return null\n }\n}\n\n/**\n * Resolve `__MAIZZLE_MSOW_{id}__` placeholders inside MSO conditional\n * comments by reading the inlined `max-width` (or `width`) of the\n * paired element marked with `data-maizzle-msow-id`.\n *\n * Used by `<Container>` and `<Section>` to derive Outlook's table width\n * from the resolved Tailwind class or inline style on the inner div,\n * after CSS inlining.\n *\n * Falls back to the value of `data-maizzle-msow-fallback` (default\n * `600px`) when the value can't be parsed.\n */\nexport function msoWidthFromClass(dom: ChildNode[]): ChildNode[] {\n const widths = new Map<string, string>()\n\n walk(dom, (node) => {\n const el = node as Element\n const id = el.attribs?.['data-maizzle-msow-id']\n if (!id) return\n delete el.attribs['data-maizzle-msow-id']\n\n const fallback = el.attribs['data-maizzle-msow-fallback'] ?? '600px'\n delete el.attribs['data-maizzle-msow-fallback']\n\n const style = el.attribs.style ?? ''\n const raw = style.match(RE_MAX_WIDTH)?.[1] ?? style.match(RE_WIDTH)?.[1]\n const resolved = raw ? resolveWidth(raw) : null\n widths.set(id, resolved ?? fallback)\n })\n\n if (widths.size === 0) return dom\n\n walk(dom, (node) => {\n if (node.type !== 'comment') return\n let data = (node as any).data as string\n if (!data.includes('__MAIZZLE_MSOW_')) return\n for (const [id, px] of widths) {\n data = data.replaceAll(`__MAIZZLE_MSOW_${id}__`, px)\n }\n ;(node as any).data = data\n })\n\n return dom\n}\n"],"mappings":";;;;AAGA,MAAM,eAAe;AACrB,MAAM,WAAW;AACjB,MAAM,aAAa;AAEnB,SAAS,aAAa,OAA8B;CAClD,MAAM,UAAU,MAAM,MAAM;AAC5B,KAAI,WAAW,KAAK,QAAQ,CAAE,QAAO;CACrC,MAAM,IAAI,QAAQ,MAAM,6BAA6B;AACrD,KAAI,CAAC,EAAG,QAAO;CACf,MAAM,IAAI,WAAW,EAAE,GAAG;AAC1B,UAAS,EAAE,MAAM,MAAM,aAAa,EAApC;EACE,KAAK,KAAM,QAAO,GAAG,KAAK,MAAM,EAAE,CAAC;EACnC,KAAK;EACL,KAAK,KAAM,QAAO,GAAG,KAAK,MAAM,IAAI,GAAG,CAAC;EACxC,KAAK,KAAM,QAAO,GAAG,KAAK,MAAM,IAAI,MAAM,CAAC;EAC3C,QAAS,QAAO;;;;;;;;;;;;;;;AAgBpB,SAAgB,kBAAkB,KAA+B;CAC/D,MAAM,yBAAS,IAAI,KAAqB;AAExC,MAAK,MAAM,SAAS;EAClB,MAAM,KAAK;EACX,MAAM,KAAK,GAAG,UAAU;AACxB,MAAI,CAAC,GAAI;AACT,SAAO,GAAG,QAAQ;EAElB,MAAM,WAAW,GAAG,QAAQ,iCAAiC;AAC7D,SAAO,GAAG,QAAQ;EAElB,MAAM,QAAQ,GAAG,QAAQ,SAAS;EAClC,MAAM,MAAM,MAAM,MAAM,aAAa,GAAG,MAAM,MAAM,MAAM,SAAS,GAAG;EACtE,MAAM,WAAW,MAAM,aAAa,IAAI,GAAG;AAC3C,SAAO,IAAI,IAAI,YAAY,SAAS;GACpC;AAEF,KAAI,OAAO,SAAS,EAAG,QAAO;AAE9B,MAAK,MAAM,SAAS;AAClB,MAAI,KAAK,SAAS,UAAW;EAC7B,IAAI,OAAQ,KAAa;AACzB,MAAI,CAAC,KAAK,SAAS,kBAAkB,CAAE;AACvC,OAAK,MAAM,CAAC,IAAI,OAAO,OACrB,QAAO,KAAK,WAAW,kBAAkB,GAAG,KAAK,GAAG;AAErD,EAAC,KAAa,OAAO;GACtB;AAEF,QAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"tailwindcss.d.mts","names":[],"sources":["../../src/transformers/tailwindcss.ts"],"mappings":";;;;;;AAoJA;;;;;;;;;;;;;;iBAAsB,WAAA,CAAY,GAAA,EAAK,SAAA,IAAa,MAAA,EAAQ,aAAA,EAAe,QAAA,YAAoB,OAAA,CAAQ,SAAA"}
1
+ {"version":3,"file":"tailwindcss.d.mts","names":[],"sources":["../../src/transformers/tailwindcss.ts"],"mappings":";;;;;;AAqIA;;;;;;;;;;;;;;iBAAsB,WAAA,CAAY,GAAA,EAAK,SAAA,IAAa,MAAA,EAAQ,aAAA,EAAe,QAAA,YAAoB,OAAA,CAAQ,SAAA"}
@@ -4,6 +4,8 @@ import resolveProps_default from "../plugins/postcss/resolveProps.mjs";
4
4
  import pruneVars_default from "../plugins/postcss/pruneVars.mjs";
5
5
  import { tailwindCleanup } from "../plugins/postcss/tailwindCleanup.mjs";
6
6
  import { mergeMediaQueries } from "../plugins/postcss/mergeMediaQueries.mjs";
7
+ import { quoteFontFamilies } from "../plugins/postcss/quoteFontFamilies.mjs";
8
+ import { decodeStyleEntities } from "../utils/decodeStyleEntities.mjs";
7
9
  import { dirname, relative, resolve } from "node:path";
8
10
  import postcss from "postcss";
9
11
  import tailwindcssPostcss from "@tailwindcss/postcss";
@@ -25,16 +27,6 @@ function createProcessor(config) {
25
27
  ]);
26
28
  }
27
29
  /**
28
- * Decode HTML entities that Vue SSR encodes inside <style> tags.
29
- *
30
- * Vue's renderToString HTML-encodes quotes and other characters
31
- * inside <style> tags within templates, breaking CSS like
32
- * `@import "@maizzle/tailwindcss"` → `@import &quot;...&quot;`
33
- */
34
- function decodeEntities(str) {
35
- return str.replace(/&quot;/g, "\"").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&#39;/g, "'").replace(/&apos;/g, "'");
36
- }
37
- /**
38
30
  * Check if CSS content uses Tailwind features that require source scanning.
39
31
  *
40
32
  * Only CSS that imports Tailwind (or @maizzle/tailwindcss) needs @source
@@ -66,7 +58,7 @@ function lowerSyntax(css) {
66
58
  * then sorts and merges media queries.
67
59
  */
68
60
  async function optimizeCss(css, config) {
69
- const plugins = [...tailwindCleanup(config)];
61
+ const plugins = [...tailwindCleanup(config), quoteFontFamilies()];
70
62
  const mediaPlugin = mergeMediaQueries(config);
71
63
  if (mediaPlugin) plugins.push(mediaPlugin);
72
64
  return (await postcss(plugins).process(css, { from: void 0 })).css;
@@ -122,7 +114,7 @@ async function tailwindcss(dom, config, filePath) {
122
114
  if (!rawContent.trim()) return;
123
115
  styleTags.push({
124
116
  node: el,
125
- cssContent: decodeEntities(rawContent)
117
+ cssContent: decodeStyleEntities(rawContent)
126
118
  });
127
119
  });
128
120
  if (!styleTags.length) return dom;
@@ -1 +1 @@
1
- {"version":3,"file":"tailwindcss.mjs","names":["resolveProps","pruneVars"],"sources":["../../src/transformers/tailwindcss.ts"],"sourcesContent":["import postcss from 'postcss'\nimport tailwindcssPostcss from '@tailwindcss/postcss'\nimport postcssCalc from 'postcss-calc'\nimport resolveProps from '../plugins/postcss/resolveProps.ts'\nimport pruneVars from '../plugins/postcss/pruneVars.ts'\nimport safeParser from 'postcss-safe-parser'\nimport { transform } from 'lightningcss'\nimport { resolve, dirname, relative } from 'node:path'\nimport type { ChildNode, Element } from 'domhandler'\nimport { walk } from '../utils/ast/index.ts'\nimport { tailwindCleanup } from '../plugins/postcss/tailwindCleanup.ts'\nimport { mergeMediaQueries } from '../plugins/postcss/mergeMediaQueries.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\nfunction createProcessor(config: MaizzleConfig) {\n return postcss([\n tailwindcssPostcss({\n base: config.css?.base,\n transformAssetUrls: false,\n optimize: false, // we run Lightning CSS manually\n }),\n resolveProps(),\n postcssCalc({}),\n pruneVars(),\n ])\n}\n\n/**\n * Decode HTML entities that Vue SSR encodes inside <style> tags.\n *\n * Vue's renderToString HTML-encodes quotes and other characters\n * inside <style> tags within templates, breaking CSS like\n * `@import \"@maizzle/tailwindcss\"` → `@import &quot;...&quot;`\n */\nfunction decodeEntities(str: string): string {\n return str\n .replace(/&quot;/g, '\"')\n .replace(/&amp;/g, '&')\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&#39;/g, \"'\")\n .replace(/&apos;/g, \"'\")\n}\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 * Lower modern CSS syntax using lightningcss.\n *\n * Targets IE 1 to maximize syntax lowering — converts modern features\n * like nesting, oklch(), color-mix(), @property, etc. into simple CSS\n * that email clients can understand.\n */\nfunction lowerSyntax(css: string): string {\n const result = transform({\n filename: 'email.css',\n code: Buffer.from(css),\n minify: false,\n targets: {\n ie: 4 << 5,\n },\n })\n\n return result.code.toString()\n}\n\n/**\n * Run cleanup and media query merging on the compiled CSS.\n *\n * Removes unwanted selectors (:host, :lang) and at-rules (@layer, @property),\n * then sorts and merges media queries.\n */\nasync function optimizeCss(css: string, config: MaizzleConfig): Promise<string> {\n const plugins: postcss.Plugin[] = [...tailwindCleanup(config)]\n\n const mediaPlugin = mergeMediaQueries(config)\n if (mediaPlugin) plugins.push(mediaPlugin)\n\n const result = await postcss(plugins).process(css, { from: undefined })\n\n return result.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 // 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 bindings).\n // We pass these raw values to Tailwind's scanner via @source inline().\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\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 // `raw` opts out of compilation entirely (marker is consumed here).\n // `embed`/`data-embed` only signal \"preserve tag after inlining\" — they\n // still need to go through compile so Tailwind/@apply resolves.\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: decodeEntities(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 // Create processor once — reused for all style tags in this template\n const processor = createProcessor(config)\n\n for (let i = 0; i < styleTags.length; i++) {\n const { node, cssContent } = styleTags[i]\n\n // Only add source directives to style tags that import Tailwind —\n // plain CSS doesn't need them and @tailwindcss/postcss would leave\n // the directives unconsumed in the output\n const fullCss = usesTailwind(cssContent)\n ? `${cssContent}\\n${sourceDirectives}`\n : cssContent\n\n try {\n const result = await processor.process(\n fullCss,\n {\n from: `${fromPath}?style=${i}`,\n parser: safeParser,\n }\n )\n\n const lowered = lowerSyntax(result.css)\n const optimized = await optimizeCss(lowered, config)\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 // If CSS processing fails, still replace with decoded content\n // so HTML entities don't break the CSS\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.mjs","names":["resolveProps","pruneVars"],"sources":["../../src/transformers/tailwindcss.ts"],"sourcesContent":["import postcss from 'postcss'\nimport tailwindcssPostcss from '@tailwindcss/postcss'\nimport postcssCalc from 'postcss-calc'\nimport resolveProps from '../plugins/postcss/resolveProps.ts'\nimport pruneVars from '../plugins/postcss/pruneVars.ts'\nimport safeParser from 'postcss-safe-parser'\nimport { transform } from 'lightningcss'\nimport { resolve, dirname, relative } from 'node:path'\nimport type { ChildNode, Element } from 'domhandler'\nimport { walk } from '../utils/ast/index.ts'\nimport { tailwindCleanup } from '../plugins/postcss/tailwindCleanup.ts'\nimport { mergeMediaQueries } from '../plugins/postcss/mergeMediaQueries.ts'\nimport { quoteFontFamilies } from '../plugins/postcss/quoteFontFamilies.ts'\nimport { decodeStyleEntities } from '../utils/decodeStyleEntities.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\nfunction createProcessor(config: MaizzleConfig) {\n return postcss([\n tailwindcssPostcss({\n base: config.css?.base,\n transformAssetUrls: false,\n optimize: false, // we run Lightning CSS manually\n }),\n resolveProps(),\n postcssCalc({}),\n pruneVars(),\n ])\n}\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 * Lower modern CSS syntax using lightningcss.\n *\n * Targets IE 1 to maximize syntax lowering — converts modern features\n * like nesting, oklch(), color-mix(), @property, etc. into simple CSS\n * that email clients can understand.\n */\nfunction lowerSyntax(css: string): string {\n const result = transform({\n filename: 'email.css',\n code: Buffer.from(css),\n minify: false,\n targets: {\n ie: 4 << 5,\n },\n })\n\n return result.code.toString()\n}\n\n/**\n * Run cleanup and media query merging on the compiled CSS.\n *\n * Removes unwanted selectors (:host, :lang) and at-rules (@layer, @property),\n * then sorts and merges media queries.\n */\nasync function optimizeCss(css: string, config: MaizzleConfig): Promise<string> {\n const plugins: postcss.Plugin[] = [...tailwindCleanup(config), quoteFontFamilies()]\n\n const mediaPlugin = mergeMediaQueries(config)\n if (mediaPlugin) plugins.push(mediaPlugin)\n\n const result = await postcss(plugins).process(css, { from: undefined })\n\n return result.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 // 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 bindings).\n // We pass these raw values to Tailwind's scanner via @source inline().\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\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 // `raw` opts out of compilation entirely (marker is consumed here).\n // `embed`/`data-embed` only signal \"preserve tag after inlining\" — they\n // still need to go through compile so Tailwind/@apply resolves.\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 // Create processor once — reused for all style tags in this template\n const processor = createProcessor(config)\n\n for (let i = 0; i < styleTags.length; i++) {\n const { node, cssContent } = styleTags[i]\n\n // Only add source directives to style tags that import Tailwind —\n // plain CSS doesn't need them and @tailwindcss/postcss would leave\n // the directives unconsumed in the output\n const fullCss = usesTailwind(cssContent)\n ? `${cssContent}\\n${sourceDirectives}`\n : cssContent\n\n try {\n const result = await processor.process(\n fullCss,\n {\n from: `${fromPath}?style=${i}`,\n parser: safeParser,\n }\n )\n\n const lowered = lowerSyntax(result.css)\n const optimized = await optimizeCss(lowered, config)\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 // If CSS processing fails, still replace with decoded content\n // so HTML entities don't break the CSS\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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"}
@@ -0,0 +1,15 @@
1
+ //#region src/utils/decodeStyleEntities.d.ts
2
+ /**
3
+ * Decode HTML entities that Vue SSR encodes inside `<style>` tags.
4
+ *
5
+ * Vue's `renderToString` HTML-encodes quotes and angle brackets within
6
+ * style elements in templates, breaking CSS like
7
+ * `@import "tailwindcss"` → `@import &quot;tailwindcss&quot;`.
8
+ *
9
+ * `&amp;` is decoded last so previously-decoded entities are not
10
+ * re-processed.
11
+ */
12
+ declare function decodeStyleEntities(s: string): string;
13
+ //#endregion
14
+ export { decodeStyleEntities };
15
+ //# sourceMappingURL=decodeStyleEntities.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decodeStyleEntities.d.mts","names":[],"sources":["../../src/utils/decodeStyleEntities.ts"],"mappings":";;AAUA;;;;;;;;;iBAAgB,mBAAA,CAAoB,CAAA"}
@@ -0,0 +1,18 @@
1
+ //#region src/utils/decodeStyleEntities.ts
2
+ /**
3
+ * Decode HTML entities that Vue SSR encodes inside `<style>` tags.
4
+ *
5
+ * Vue's `renderToString` HTML-encodes quotes and angle brackets within
6
+ * style elements in templates, breaking CSS like
7
+ * `@import "tailwindcss"` → `@import &quot;tailwindcss&quot;`.
8
+ *
9
+ * `&amp;` is decoded last so previously-decoded entities are not
10
+ * re-processed.
11
+ */
12
+ function decodeStyleEntities(s) {
13
+ return s.replace(/&quot;/g, "\"").replace(/&#39;/g, "'").replace(/&apos;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
14
+ }
15
+
16
+ //#endregion
17
+ export { decodeStyleEntities };
18
+ //# sourceMappingURL=decodeStyleEntities.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decodeStyleEntities.mjs","names":[],"sources":["../../src/utils/decodeStyleEntities.ts"],"sourcesContent":["/**\n * Decode HTML entities that Vue SSR encodes inside `<style>` tags.\n *\n * Vue's `renderToString` HTML-encodes quotes and angle brackets within\n * style elements in templates, breaking CSS like\n * `@import \"tailwindcss\"` → `@import &quot;tailwindcss&quot;`.\n *\n * `&amp;` is decoded last so previously-decoded entities are not\n * re-processed.\n */\nexport function decodeStyleEntities(s: string): string {\n return s\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&apos;/g, \"'\")\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&amp;/g, '&')\n}\n"],"mappings":";;;;;;;;;;;AAUA,SAAgB,oBAAoB,GAAmB;AACrD,QAAO,EACJ,QAAQ,WAAW,KAAI,CACvB,QAAQ,UAAU,IAAI,CACtB,QAAQ,WAAW,IAAI,CACvB,QAAQ,SAAS,IAAI,CACrB,QAAQ,SAAS,IAAI,CACrB,QAAQ,UAAU,IAAI"}
@@ -3,7 +3,7 @@ import color from "picocolors";
3
3
  import * as p from "@clack/prompts";
4
4
  import { rm } from "node:fs/promises";
5
5
  import { existsSync } from "node:fs";
6
- import { execFileSync } from "node:child_process";
6
+ import { execSync } from "node:child_process";
7
7
  import { installDependencies } from "nypm";
8
8
  //#region src/commands/new.ts
9
9
  const starters = [
@@ -210,7 +210,7 @@ async function newProject(starterArg, dirArg, options = {}) {
210
210
  message: "Install dependencies?",
211
211
  initialValue: true
212
212
  }),
213
- pm: async () => "npm"
213
+ pm: async () => options.pm || "npm"
214
214
  }, { onCancel: () => {
215
215
  p.cancel("💀");
216
216
  process.exit(0);
@@ -228,7 +228,7 @@ async function newProject(starterArg, dirArg, options = {}) {
228
228
  spinner.stop(`Created project in ${project.path}`);
229
229
  if (project.install) {
230
230
  try {
231
- execFileSync(project.pm, ["--version"], { stdio: "ignore" });
231
+ execSync(`${project.pm} --version`, { stdio: "ignore" });
232
232
  } catch {
233
233
  p.log.error(`${project.pm} is not installed. Please install it first.`);
234
234
  process.exit(1);
@@ -2,6 +2,7 @@
2
2
  interface Framework {
3
3
  serve: (options?: any) => Promise<any>;
4
4
  build: (options?: any) => Promise<any>;
5
+ prepare: (options?: any) => Promise<any>;
5
6
  }
6
7
  declare function bootstrap(framework?: Framework): Promise<void>;
7
8
  //#endregion
@@ -22,6 +22,9 @@ async function bootstrap(framework) {
22
22
  output: options.output
23
23
  });
24
24
  });
25
+ program.command("prepare").description("Generate IDE type definitions in .maizzle/").option("-c, --config <path>", "Path to maizzle config file").action(async (options) => {
26
+ await framework.prepare({ config: options.config });
27
+ });
25
28
  }
26
29
  program.command("new [starter] [directory]").description("Create a new Maizzle project").option("-i, --install", "Install dependencies").option("--pm <manager>", "Package manager to use").action(async (starter, directory, options) => {
27
30
  await newProject(starter, directory, options);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maizzle",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "CLI tool for the Maizzle Email Framework",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,7 +19,8 @@
19
19
  ],
20
20
  "scripts": {
21
21
  "build": "tsdown",
22
- "prepublishOnly": "npm run build",
22
+ "typecheck": "tsc --noEmit",
23
+ "prepublishOnly": "npm run typecheck && npm run build",
23
24
  "dev": "vitest",
24
25
  "test": "vitest run --coverage",
25
26
  "release": "npm run build && npx np"