@maizzle/framework 6.0.0 → 6.0.1

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.
@@ -73,7 +73,7 @@ function inlineCssDom(dom, options = {}) {
73
73
  applyHeightAttributes: juicePassthrough.applyHeightAttributes ?? true,
74
74
  preservedSelectors: safelist ?? [],
75
75
  ...customCSS ? { extraCss: customCSS } : {},
76
- inlineDuplicateProperties: juicePassthrough.inlineDuplicateProperties ?? true,
76
+ inlineDuplicateProperties: juicePassthrough.inlineDuplicateProperties ?? false,
77
77
  ...juicePassthrough
78
78
  });
79
79
  } catch {
@@ -1 +1 @@
1
- {"version":3,"file":"inlineCss.js","names":[],"sources":["../../src/transformers/inlineCss.ts"],"sourcesContent":["import juice from 'juice'\nimport { walk, parse, serialize } from '../utils/ast/index.ts'\n\n/** Juice's built-in code blocks, captured before any per-call mutation. */\nconst DEFAULT_CODE_BLOCKS = { ...juice.codeBlocks }\nimport type { ChildNode, Element } from 'domhandler'\ntype JuiceOptions = NonNullable<Parameters<typeof juice>[1]>\n\n/**\n * Options for the `inlineCss` transformer. Accepts every Juice option plus a\n * handful of Maizzle-specific extras.\n */\nexport interface InlineCssOptions extends JuiceOptions {\n /**\n * Convert `0px`, `0em` etc. to `0` in inline styles.\n *\n * @default true\n */\n preferUnitlessValues?: boolean\n /**\n * CSS selectors to preserve in `<style>` tags, even after inlining.\n * Mapped to Juice's `preservedSelectors` option.\n *\n * @default []\n */\n safelist?: string[]\n /**\n * Additional CSS string to inline alongside `<style>` tag contents.\n * Mapped to Juice's `extraCss` option.\n */\n customCSS?: string\n /**\n * Duplicate CSS properties to HTML attributes.\n * Mapped to Juice's static `styleToAttribute` property.\n */\n styleToAttribute?: Record<string, string>\n /**\n * CSS properties to exclude from inlining.\n * Mapped to Juice's static `excludedProperties` property.\n */\n excludedProperties?: string[]\n /**\n * Elements that can receive `width` HTML attributes.\n * Mapped to Juice's static `widthElements` property.\n *\n * @default ['img', 'video']\n */\n widthElements?: string[]\n /**\n * Elements that can receive `height` HTML attributes.\n * Mapped to Juice's static `heightElements` property.\n *\n * @default ['img', 'video']\n */\n heightElements?: string[]\n /**\n * Template language code blocks to preserve during inlining.\n * Mapped to Juice's static `codeBlocks` property.\n */\n codeBlocks?: Record<string, { start: string; end: string }>\n}\n\n/**\n * Inline CSS from `<style>` tags into `style` attributes on matching elements.\n *\n * @param html HTML string to transform.\n * @param options Juice options plus Maizzle-specific extras.\n * @returns The transformed HTML string.\n *\n * @example\n * import { inlineCss } from '@maizzle/framework'\n *\n * const out = inlineCss('<style>.red{color:red}</style><p class=\"red\">x</p>', {\n * removeStyleTags: true,\n * })\n */\nexport function inlineCss(html: string, options: InlineCssOptions = {}): string {\n return serialize(inlineCssDom(parse(html), options))\n}\n\n/**\n * DOM-form of {@link inlineCss} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids the redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function inlineCssDom(dom: ChildNode[], options: InlineCssOptions = {}): ChildNode[] {\n const {\n preferUnitlessValues = true,\n safelist,\n customCSS = '',\n styleToAttribute,\n excludedProperties,\n widthElements,\n heightElements,\n codeBlocks,\n ...juicePassthrough\n } = options\n\n // Configure Juice static properties\n juice.styleToAttribute = styleToAttribute ?? {}\n juice.excludedProperties = ['--tw-shadow', ...(excludedProperties ?? [])]\n juice.widthElements = (widthElements ?? ['img', 'video']).map(i => i.toUpperCase()) as unknown as HTMLElement[]\n juice.heightElements = (heightElements ?? ['img', 'video']).map(i => i.toUpperCase()) as unknown as HTMLElement[]\n\n // Reset to defaults each call so a previous template's custom code blocks\n // don't accumulate on juice's module-global state, then add this call's.\n juice.codeBlocks = { ...DEFAULT_CODE_BLOCKS }\n if (codeBlocks && typeof codeBlocks === 'object') {\n Object.entries(codeBlocks).forEach(([key, value]) => {\n if (value.start && value.end) {\n juice.codeBlocks[key] = value\n }\n })\n }\n\n /**\n * Handle style tags with embed attributes. We add a marker attribute\n * that persists through the pipeline, then restore data-embed from\n * it after Juice runs. `amp-custom` (AMP4Email's CSS attribute)\n * is treated the same as embed: contents are preserved,\n * never inlined.\n */\n walk(dom, (node) => {\n const el = node as Element\n if (el.name === 'style' && el.attribs) {\n /**\n * `amp-custom` → tell juice to skip via data-embed, but don't mirror\n * back to `embed` (user wrote amp-custom, that's what stays in\n * output).\n */\n if ('amp-custom' in el.attribs && !('data-embed' in el.attribs)) {\n el.attribs['data-embed'] = ''\n }\n /**\n * Sync data-embed ↔ embed. Use `in` so presence-only attrs\n * (<style embed> → attribs.embed === '') still count.\n */\n if ('embed' in el.attribs && !('data-embed' in el.attribs)) {\n el.attribs['data-embed'] = ''\n }\n if ('data-embed' in el.attribs && !('embed' in el.attribs) && !('amp-custom' in el.attribs)) {\n el.attribs.embed = ''\n }\n\n // Add marker that persists through the pipeline\n if ('data-embed' in el.attribs) {\n el.attribs['data-maizzle-embed'] = ''\n }\n }\n })\n\n // Serialize for juice (juice requires a string)\n const serialized = serialize(dom)\n\n let inlinedHtml: string\n\n try {\n const juiceOptions: JuiceOptions = {\n removeStyleTags: juicePassthrough.removeStyleTags ?? false,\n removeInlinedSelectors: juicePassthrough.removeInlinedSelectors ?? true,\n applyWidthAttributes: juicePassthrough.applyWidthAttributes ?? true,\n applyHeightAttributes: juicePassthrough.applyHeightAttributes ?? true,\n preservedSelectors: safelist ?? [],\n ...customCSS ? { extraCss: customCSS } : {},\n inlineDuplicateProperties: juicePassthrough.inlineDuplicateProperties ?? true,\n ...juicePassthrough,\n }\n\n inlinedHtml = juice(serialized, juiceOptions)\n } catch {\n // If Juice fails, return the dom unchanged\n return dom\n }\n\n const result = parse(inlinedHtml)\n\n if (preferUnitlessValues) {\n walk(result, (node) => {\n const el = node as Element\n if (el.attribs?.style) {\n el.attribs.style = el.attribs.style.replace(\n /\\b0(px|rem|em|%|vh|vw|vmin|vmax|in|cm|mm|pt|pc|ex|ch)\\b/g,\n '0'\n )\n }\n })\n }\n\n /**\n * Restore `embed` from our marker so the purge step can detect\n * these tags and skip them. Drop `data-embed` (juice's name)\n * since it's redundant once `embed` is back, and let purge\n * strip `embed` itself at the end of its run.\n */\n walk(result, (node) => {\n const el = node as Element\n if (el.name === 'style' && el.attribs && 'data-maizzle-embed' in el.attribs) {\n /**\n * Only restore `embed` when the original signal was embed/data-embed —\n * an `amp-custom` style was tagged for juice's benefit only and\n * must not pick up a stray `embed` attribute in the rendered\n * output.\n */\n if (!('amp-custom' in el.attribs)) {\n el.attribs.embed = ''\n }\n delete el.attribs['data-embed']\n delete el.attribs['data-maizzle-embed']\n }\n })\n\n return result\n}\n"],"mappings":";;;;;;;AAIA,MAAM,sBAAsB,EAAE,GAAG,MAAM,WAAW;;;;;;;;;;;;;;;AAwElD,SAAgB,UAAU,MAAc,UAA4B,CAAC,GAAW;CAC9E,OAAO,UAAU,aAAa,MAAM,IAAI,GAAG,OAAO,CAAC;AACrD;;;;;;AAOA,SAAgB,aAAa,KAAkB,UAA4B,CAAC,GAAgB;CAC1F,MAAM,EACJ,uBAAuB,MACvB,UACA,YAAY,IACZ,kBACA,oBACA,eACA,gBACA,YACA,GAAG,qBACD;CAGJ,MAAM,mBAAmB,oBAAoB,CAAC;CAC9C,MAAM,qBAAqB,CAAC,eAAe,GAAI,sBAAsB,CAAC,CAAE;CACxE,MAAM,iBAAiB,iBAAiB,CAAC,OAAO,OAAO,EAAA,CAAG,KAAI,MAAK,EAAE,YAAY,CAAC;CAClF,MAAM,kBAAkB,kBAAkB,CAAC,OAAO,OAAO,EAAA,CAAG,KAAI,MAAK,EAAE,YAAY,CAAC;CAIpF,MAAM,aAAa,EAAE,GAAG,oBAAoB;CAC5C,IAAI,cAAc,OAAO,eAAe,UACtC,OAAO,QAAQ,UAAU,CAAC,CAAC,SAAS,CAAC,KAAK,WAAW;EACnD,IAAI,MAAM,SAAS,MAAM,KACvB,MAAM,WAAW,OAAO;CAE5B,CAAC;;;;;;;;CAUH,KAAK,MAAM,SAAS;EAClB,MAAM,KAAK;EACX,IAAI,GAAG,SAAS,WAAW,GAAG,SAAS;;;;;;GAMrC,IAAI,gBAAgB,GAAG,WAAW,EAAE,gBAAgB,GAAG,UACrD,GAAG,QAAQ,gBAAgB;;;;;GAM7B,IAAI,WAAW,GAAG,WAAW,EAAE,gBAAgB,GAAG,UAChD,GAAG,QAAQ,gBAAgB;GAE7B,IAAI,gBAAgB,GAAG,WAAW,EAAE,WAAW,GAAG,YAAY,EAAE,gBAAgB,GAAG,UACjF,GAAG,QAAQ,QAAQ;GAIrB,IAAI,gBAAgB,GAAG,SACrB,GAAG,QAAQ,wBAAwB;EAEvC;CACF,CAAC;CAGD,MAAM,aAAa,UAAU,GAAG;CAEhC,IAAI;CAEJ,IAAI;EAYF,cAAc,MAAM,YAAY;GAV9B,iBAAiB,iBAAiB,mBAAmB;GACrD,wBAAwB,iBAAiB,0BAA0B;GACnE,sBAAsB,iBAAiB,wBAAwB;GAC/D,uBAAuB,iBAAiB,yBAAyB;GACjE,oBAAoB,YAAY,CAAC;GACjC,GAAG,YAAY,EAAE,UAAU,UAAU,IAAI,CAAC;GAC1C,2BAA2B,iBAAiB,6BAA6B;GACzE,GAAG;EAGsC,CAAC;CAC9C,QAAQ;EAEN,OAAO;CACT;CAEA,MAAM,SAAS,MAAM,WAAW;CAEhC,IAAI,sBACF,KAAK,SAAS,SAAS;EACrB,MAAM,KAAK;EACX,IAAI,GAAG,SAAS,OACd,GAAG,QAAQ,QAAQ,GAAG,QAAQ,MAAM,QAClC,4DACA,GACF;CAEJ,CAAC;;;;;;;CASH,KAAK,SAAS,SAAS;EACrB,MAAM,KAAK;EACX,IAAI,GAAG,SAAS,WAAW,GAAG,WAAW,wBAAwB,GAAG,SAAS;;;;;;;GAO3E,IAAI,EAAE,gBAAgB,GAAG,UACvB,GAAG,QAAQ,QAAQ;GAErB,OAAO,GAAG,QAAQ;GAClB,OAAO,GAAG,QAAQ;EACpB;CACF,CAAC;CAED,OAAO;AACT"}
1
+ {"version":3,"file":"inlineCss.js","names":[],"sources":["../../src/transformers/inlineCss.ts"],"sourcesContent":["import juice from 'juice'\nimport { walk, parse, serialize } from '../utils/ast/index.ts'\n\n/** Juice's built-in code blocks, captured before any per-call mutation. */\nconst DEFAULT_CODE_BLOCKS = { ...juice.codeBlocks }\nimport type { ChildNode, Element } from 'domhandler'\ntype JuiceOptions = NonNullable<Parameters<typeof juice>[1]>\n\n/**\n * Options for the `inlineCss` transformer. Accepts every Juice option plus a\n * handful of Maizzle-specific extras.\n */\nexport interface InlineCssOptions extends JuiceOptions {\n /**\n * Convert `0px`, `0em` etc. to `0` in inline styles.\n *\n * @default true\n */\n preferUnitlessValues?: boolean\n /**\n * CSS selectors to preserve in `<style>` tags, even after inlining.\n * Mapped to Juice's `preservedSelectors` option.\n *\n * @default []\n */\n safelist?: string[]\n /**\n * Additional CSS string to inline alongside `<style>` tag contents.\n * Mapped to Juice's `extraCss` option.\n */\n customCSS?: string\n /**\n * Duplicate CSS properties to HTML attributes.\n * Mapped to Juice's static `styleToAttribute` property.\n */\n styleToAttribute?: Record<string, string>\n /**\n * CSS properties to exclude from inlining.\n * Mapped to Juice's static `excludedProperties` property.\n */\n excludedProperties?: string[]\n /**\n * Elements that can receive `width` HTML attributes.\n * Mapped to Juice's static `widthElements` property.\n *\n * @default ['img', 'video']\n */\n widthElements?: string[]\n /**\n * Elements that can receive `height` HTML attributes.\n * Mapped to Juice's static `heightElements` property.\n *\n * @default ['img', 'video']\n */\n heightElements?: string[]\n /**\n * Template language code blocks to preserve during inlining.\n * Mapped to Juice's static `codeBlocks` property.\n */\n codeBlocks?: Record<string, { start: string; end: string }>\n}\n\n/**\n * Inline CSS from `<style>` tags into `style` attributes on matching elements.\n *\n * @param html HTML string to transform.\n * @param options Juice options plus Maizzle-specific extras.\n * @returns The transformed HTML string.\n *\n * @example\n * import { inlineCss } from '@maizzle/framework'\n *\n * const out = inlineCss('<style>.red{color:red}</style><p class=\"red\">x</p>', {\n * removeStyleTags: true,\n * })\n */\nexport function inlineCss(html: string, options: InlineCssOptions = {}): string {\n return serialize(inlineCssDom(parse(html), options))\n}\n\n/**\n * DOM-form of {@link inlineCss} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids the redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function inlineCssDom(dom: ChildNode[], options: InlineCssOptions = {}): ChildNode[] {\n const {\n preferUnitlessValues = true,\n safelist,\n customCSS = '',\n styleToAttribute,\n excludedProperties,\n widthElements,\n heightElements,\n codeBlocks,\n ...juicePassthrough\n } = options\n\n // Configure Juice static properties\n juice.styleToAttribute = styleToAttribute ?? {}\n juice.excludedProperties = ['--tw-shadow', ...(excludedProperties ?? [])]\n juice.widthElements = (widthElements ?? ['img', 'video']).map(i => i.toUpperCase()) as unknown as HTMLElement[]\n juice.heightElements = (heightElements ?? ['img', 'video']).map(i => i.toUpperCase()) as unknown as HTMLElement[]\n\n // Reset to defaults each call so a previous template's custom code blocks\n // don't accumulate on juice's module-global state, then add this call's.\n juice.codeBlocks = { ...DEFAULT_CODE_BLOCKS }\n if (codeBlocks && typeof codeBlocks === 'object') {\n Object.entries(codeBlocks).forEach(([key, value]) => {\n if (value.start && value.end) {\n juice.codeBlocks[key] = value\n }\n })\n }\n\n /**\n * Handle style tags with embed attributes. We add a marker attribute\n * that persists through the pipeline, then restore data-embed from\n * it after Juice runs. `amp-custom` (AMP4Email's CSS attribute)\n * is treated the same as embed: contents are preserved,\n * never inlined.\n */\n walk(dom, (node) => {\n const el = node as Element\n if (el.name === 'style' && el.attribs) {\n /**\n * `amp-custom` → tell juice to skip via data-embed, but don't mirror\n * back to `embed` (user wrote amp-custom, that's what stays in\n * output).\n */\n if ('amp-custom' in el.attribs && !('data-embed' in el.attribs)) {\n el.attribs['data-embed'] = ''\n }\n /**\n * Sync data-embed ↔ embed. Use `in` so presence-only attrs\n * (<style embed> → attribs.embed === '') still count.\n */\n if ('embed' in el.attribs && !('data-embed' in el.attribs)) {\n el.attribs['data-embed'] = ''\n }\n if ('data-embed' in el.attribs && !('embed' in el.attribs) && !('amp-custom' in el.attribs)) {\n el.attribs.embed = ''\n }\n\n // Add marker that persists through the pipeline\n if ('data-embed' in el.attribs) {\n el.attribs['data-maizzle-embed'] = ''\n }\n }\n })\n\n // Serialize for juice (juice requires a string)\n const serialized = serialize(dom)\n\n let inlinedHtml: string\n\n try {\n const juiceOptions: JuiceOptions = {\n removeStyleTags: juicePassthrough.removeStyleTags ?? false,\n removeInlinedSelectors: juicePassthrough.removeInlinedSelectors ?? true,\n applyWidthAttributes: juicePassthrough.applyWidthAttributes ?? true,\n applyHeightAttributes: juicePassthrough.applyHeightAttributes ?? true,\n preservedSelectors: safelist ?? [],\n ...customCSS ? { extraCss: customCSS } : {},\n inlineDuplicateProperties: juicePassthrough.inlineDuplicateProperties ?? false,\n ...juicePassthrough,\n }\n\n inlinedHtml = juice(serialized, juiceOptions)\n } catch {\n // If Juice fails, return the dom unchanged\n return dom\n }\n\n const result = parse(inlinedHtml)\n\n if (preferUnitlessValues) {\n walk(result, (node) => {\n const el = node as Element\n if (el.attribs?.style) {\n el.attribs.style = el.attribs.style.replace(\n /\\b0(px|rem|em|%|vh|vw|vmin|vmax|in|cm|mm|pt|pc|ex|ch)\\b/g,\n '0'\n )\n }\n })\n }\n\n /**\n * Restore `embed` from our marker so the purge step can detect\n * these tags and skip them. Drop `data-embed` (juice's name)\n * since it's redundant once `embed` is back, and let purge\n * strip `embed` itself at the end of its run.\n */\n walk(result, (node) => {\n const el = node as Element\n if (el.name === 'style' && el.attribs && 'data-maizzle-embed' in el.attribs) {\n /**\n * Only restore `embed` when the original signal was embed/data-embed —\n * an `amp-custom` style was tagged for juice's benefit only and\n * must not pick up a stray `embed` attribute in the rendered\n * output.\n */\n if (!('amp-custom' in el.attribs)) {\n el.attribs.embed = ''\n }\n delete el.attribs['data-embed']\n delete el.attribs['data-maizzle-embed']\n }\n })\n\n return result\n}\n"],"mappings":";;;;;;;AAIA,MAAM,sBAAsB,EAAE,GAAG,MAAM,WAAW;;;;;;;;;;;;;;;AAwElD,SAAgB,UAAU,MAAc,UAA4B,CAAC,GAAW;CAC9E,OAAO,UAAU,aAAa,MAAM,IAAI,GAAG,OAAO,CAAC;AACrD;;;;;;AAOA,SAAgB,aAAa,KAAkB,UAA4B,CAAC,GAAgB;CAC1F,MAAM,EACJ,uBAAuB,MACvB,UACA,YAAY,IACZ,kBACA,oBACA,eACA,gBACA,YACA,GAAG,qBACD;CAGJ,MAAM,mBAAmB,oBAAoB,CAAC;CAC9C,MAAM,qBAAqB,CAAC,eAAe,GAAI,sBAAsB,CAAC,CAAE;CACxE,MAAM,iBAAiB,iBAAiB,CAAC,OAAO,OAAO,EAAA,CAAG,KAAI,MAAK,EAAE,YAAY,CAAC;CAClF,MAAM,kBAAkB,kBAAkB,CAAC,OAAO,OAAO,EAAA,CAAG,KAAI,MAAK,EAAE,YAAY,CAAC;CAIpF,MAAM,aAAa,EAAE,GAAG,oBAAoB;CAC5C,IAAI,cAAc,OAAO,eAAe,UACtC,OAAO,QAAQ,UAAU,CAAC,CAAC,SAAS,CAAC,KAAK,WAAW;EACnD,IAAI,MAAM,SAAS,MAAM,KACvB,MAAM,WAAW,OAAO;CAE5B,CAAC;;;;;;;;CAUH,KAAK,MAAM,SAAS;EAClB,MAAM,KAAK;EACX,IAAI,GAAG,SAAS,WAAW,GAAG,SAAS;;;;;;GAMrC,IAAI,gBAAgB,GAAG,WAAW,EAAE,gBAAgB,GAAG,UACrD,GAAG,QAAQ,gBAAgB;;;;;GAM7B,IAAI,WAAW,GAAG,WAAW,EAAE,gBAAgB,GAAG,UAChD,GAAG,QAAQ,gBAAgB;GAE7B,IAAI,gBAAgB,GAAG,WAAW,EAAE,WAAW,GAAG,YAAY,EAAE,gBAAgB,GAAG,UACjF,GAAG,QAAQ,QAAQ;GAIrB,IAAI,gBAAgB,GAAG,SACrB,GAAG,QAAQ,wBAAwB;EAEvC;CACF,CAAC;CAGD,MAAM,aAAa,UAAU,GAAG;CAEhC,IAAI;CAEJ,IAAI;EAYF,cAAc,MAAM,YAAY;GAV9B,iBAAiB,iBAAiB,mBAAmB;GACrD,wBAAwB,iBAAiB,0BAA0B;GACnE,sBAAsB,iBAAiB,wBAAwB;GAC/D,uBAAuB,iBAAiB,yBAAyB;GACjE,oBAAoB,YAAY,CAAC;GACjC,GAAG,YAAY,EAAE,UAAU,UAAU,IAAI,CAAC;GAC1C,2BAA2B,iBAAiB,6BAA6B;GACzE,GAAG;EAGsC,CAAC;CAC9C,QAAQ;EAEN,OAAO;CACT;CAEA,MAAM,SAAS,MAAM,WAAW;CAEhC,IAAI,sBACF,KAAK,SAAS,SAAS;EACrB,MAAM,KAAK;EACX,IAAI,GAAG,SAAS,OACd,GAAG,QAAQ,QAAQ,GAAG,QAAQ,MAAM,QAClC,4DACA,GACF;CAEJ,CAAC;;;;;;;CASH,KAAK,SAAS,SAAS;EACrB,MAAM,KAAK;EACX,IAAI,GAAG,SAAS,WAAW,GAAG,WAAW,wBAAwB,GAAG,SAAS;;;;;;;GAO3E,IAAI,EAAE,gBAAgB,GAAG,UACvB,GAAG,QAAQ,QAAQ;GAErB,OAAO,GAAG,QAAQ;GAClB,OAAO,GAAG,QAAQ;EACpB;CACF,CAAC;CAED,OAAO;AACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maizzle/framework",
3
- "version": "6.0.0",
3
+ "version": "6.0.1",
4
4
  "description": "Maizzle is a framework that helps you quickly build HTML emails with Tailwind CSS.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -56,7 +56,7 @@
56
56
  "juice": "^12.0.0",
57
57
  "maizzle": "latest",
58
58
  "markdown-exit": "^1.1.0-beta.2",
59
- "nodemailer": "^8.0.5",
59
+ "nodemailer": "^9.0.0",
60
60
  "ora": "^9.3.0",
61
61
  "oxfmt": "^0.53.0",
62
62
  "pathe": "^2.0.3",