@maizzle/framework 6.0.0-rc.24 → 6.0.0-rc.25
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.
- package/dist/build.js.map +1 -1
- package/dist/components/Body.vue +12 -0
- package/dist/components/Button.vue +16 -29
- package/dist/components/CodeBlock.vue +5 -4
- package/dist/components/CodeInline.vue +9 -8
- package/dist/components/Column.vue +17 -4
- package/dist/components/Container.vue +7 -11
- package/dist/components/Hr.vue +1 -1
- package/dist/components/Img.vue +39 -22
- package/dist/components/Layout.vue +1 -1
- package/dist/components/Markdown.vue +9 -14
- package/dist/components/QrCode.vue +2 -2
- package/dist/components/Section.vue +9 -6
- package/dist/components/Text.vue +2 -2
- package/dist/components/utils.d.ts +25 -1
- package/dist/components/utils.d.ts.map +1 -1
- package/dist/components/utils.js +29 -1
- package/dist/components/utils.js.map +1 -1
- package/dist/components/utils.ts +46 -0
- package/dist/composables/useConfig.d.ts.map +1 -1
- package/dist/composables/useCurrentTemplate.d.ts.map +1 -1
- package/dist/composables/useEvent.d.ts.map +1 -1
- package/dist/composables/useFont.js.map +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js.map +1 -1
- package/dist/plaintext.js.map +1 -1
- package/dist/plugins/postcss/mergeMediaQueries.js.map +1 -1
- package/dist/plugins/postcss/pruneVars.js.map +1 -1
- package/dist/plugins/postcss/quoteFontFamilies.d.ts.map +1 -1
- package/dist/plugins/postcss/quoteFontFamilies.js.map +1 -1
- package/dist/plugins/postcss/removeDeclarations.js.map +1 -1
- package/dist/plugins/postcss/resolveProps.d.ts.map +1 -1
- package/dist/plugins/postcss/resolveProps.js +0 -3
- package/dist/plugins/postcss/resolveProps.js.map +1 -1
- package/dist/plugins/postcss/tailwindCleanup.js.map +1 -1
- package/dist/prepare.js +1 -1
- package/dist/prepare.js.map +1 -1
- package/dist/render/active.d.ts.map +1 -1
- package/dist/render/createRenderer.d.ts.map +1 -1
- package/dist/render/createRenderer.js +3 -7
- package/dist/render/createRenderer.js.map +1 -1
- package/dist/render/index.js.map +1 -1
- package/dist/render/injectFonts.js.map +1 -1
- package/dist/render/plugins/codeBlockExtract.d.ts.map +1 -1
- package/dist/render/plugins/codeBlockExtract.js.map +1 -1
- package/dist/render/plugins/markdownExtract.d.ts.map +1 -1
- package/dist/render/plugins/markdownExtract.js.map +1 -1
- package/dist/render/plugins/rawExtract.d.ts.map +1 -1
- package/dist/render/plugins/rawExtract.js.map +1 -1
- package/dist/render/plugins/rowSourceLocation.d.ts.map +1 -1
- package/dist/render/plugins/rowSourceLocation.js.map +1 -1
- package/dist/serve.js.map +1 -1
- package/dist/server/compatibility.d.ts.map +1 -1
- package/dist/server/compatibility.js.map +1 -1
- package/dist/server/linter.js.map +1 -1
- package/dist/server/sfc-utils.js.map +1 -1
- package/dist/tests/render/_helpers.d.ts.map +1 -1
- package/dist/tests/render/_helpers.js.map +1 -1
- package/dist/transformers/addAttributes.js +2 -3
- package/dist/transformers/addAttributes.js.map +1 -1
- package/dist/transformers/base.d.ts +1 -1
- package/dist/transformers/base.d.ts.map +1 -1
- package/dist/transformers/base.js +5 -10
- package/dist/transformers/base.js.map +1 -1
- package/dist/transformers/columnWidth.d.ts.map +1 -1
- package/dist/transformers/columnWidth.js +2 -7
- package/dist/transformers/columnWidth.js.map +1 -1
- package/dist/transformers/entities.js.map +1 -1
- package/dist/transformers/filters/defaults.js.map +1 -1
- package/dist/transformers/filters/index.js.map +1 -1
- package/dist/transformers/format.js.map +1 -1
- package/dist/transformers/imgWidth.d.ts +20 -0
- package/dist/transformers/imgWidth.d.ts.map +1 -0
- package/dist/transformers/imgWidth.js +76 -0
- package/dist/transformers/imgWidth.js.map +1 -0
- package/dist/transformers/index.d.ts.map +1 -1
- package/dist/transformers/index.js +2 -0
- package/dist/transformers/index.js.map +1 -1
- package/dist/transformers/inlineCss.d.ts +3 -2
- package/dist/transformers/inlineCss.d.ts.map +1 -1
- package/dist/transformers/inlineCss.js.map +1 -1
- package/dist/transformers/inlineLink.js +1 -1
- package/dist/transformers/inlineLink.js.map +1 -1
- package/dist/transformers/minify.js.map +1 -1
- package/dist/transformers/minifyCodeInline.js.map +1 -1
- package/dist/transformers/msoPlaceholders.d.ts.map +1 -1
- package/dist/transformers/msoPlaceholders.js +2 -7
- package/dist/transformers/msoPlaceholders.js.map +1 -1
- package/dist/transformers/purgeCss.js.map +1 -1
- package/dist/transformers/replaceStrings.js.map +1 -1
- package/dist/transformers/safeSelectors.js.map +1 -1
- package/dist/transformers/shorthandCss.js.map +1 -1
- package/dist/transformers/tailwindComponent.js.map +1 -1
- package/dist/transformers/tailwindcss.js +1 -1
- package/dist/transformers/tailwindcss.js.map +1 -1
- package/dist/transformers/urlQuery.js.map +1 -1
- package/dist/types/config.d.ts +5 -4
- package/dist/types/config.d.ts.map +1 -1
- package/dist/utils/ast/serializer.js.map +1 -1
- package/dist/utils/compileTailwindCss.js.map +1 -1
- package/dist/utils/componentSources.js.map +1 -1
- package/dist/utils/cssBox.d.ts.map +1 -1
- package/dist/utils/cssBox.js +2 -7
- package/dist/utils/cssBox.js.map +1 -1
- package/dist/utils/decodeStyleEntities.js.map +1 -1
- package/dist/utils/url.js.map +1 -1
- package/dist/utils/watchPaths.js.map +1 -1
- package/node_modules/@clack/core/CHANGELOG.md +30 -0
- package/node_modules/@clack/core/dist/index.d.mts +109 -3
- package/node_modules/@clack/core/dist/index.mjs +972 -17
- package/node_modules/@clack/core/package.json +2 -1
- package/node_modules/@clack/prompts/CHANGELOG.md +42 -0
- package/node_modules/@clack/prompts/README.md +30 -9
- package/node_modules/@clack/prompts/dist/index.d.mts +458 -27
- package/node_modules/@clack/prompts/dist/index.mjs +1378 -141
- package/node_modules/@clack/prompts/package.json +2 -2
- package/node_modules/tinyexec/package.json +4 -4
- package/package.json +8 -7
- package/dist/components/Overlap.vue +0 -156
- package/node_modules/@clack/core/dist/index.mjs.map +0 -1
- package/node_modules/@clack/prompts/dist/index.mjs.map +0 -1
|
@@ -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'\nimport type { ChildNode, Element } from 'domhandler'\
|
|
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'\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 // Add custom 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":";;;;;;;;;;;;;;;;;;;;AAyEA,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;CAGpF,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"}
|
|
@@ -40,7 +40,7 @@ async function inlineLinkDom(dom, filePath) {
|
|
|
40
40
|
walk(dom, (node) => {
|
|
41
41
|
if (node.name !== "link") return;
|
|
42
42
|
const el = node;
|
|
43
|
-
const attrs = el.attribs
|
|
43
|
+
const attrs = el.attribs;
|
|
44
44
|
if (attrs.rel !== "stylesheet" || !attrs.href) return;
|
|
45
45
|
const parent = el.parent;
|
|
46
46
|
if (parent && "children" in parent) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inlineLink.js","names":["parse"],"sources":["../../src/transformers/inlineLink.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { resolve, dirname } from 'node:path'\nimport type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize, walk } from '../utils/ast/index.ts'\n\n/**\n * Inline `<link rel=\"stylesheet\">` tags as `<style>` tags.\n *\n * - Local file paths are inlined when `filePath` is provided (resolved\n * relative to it).\n * - Remote URLs (`http://` / `https://`) are only inlined when the link\n * carries an `inline` attribute, e.g. `<link rel=\"stylesheet\" inline href=\"…\">`.\n *\n * @param html HTML string to transform.\n * @param filePath Path of the source file the HTML came from, used as the\n * base for resolving relative `href` values. Required for\n * local-file inlining; remote `inline` links work without it.\n * @returns The transformed HTML string.\n *\n * @example\n * import { inlineLink } from '@maizzle/framework'\n *\n * const out = await inlineLink(\n * '<link rel=\"stylesheet\" href=\"./styles.css\">',\n * '/path/to/template.html',\n * )\n */\nexport async function inlineLink(html: string, filePath?: string): Promise<string> {\n return serialize(await inlineLinkDom(parse(html), filePath))\n}\n\n/**\n * DOM-form of {@link inlineLink} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport async function inlineLinkDom(dom: ChildNode[], filePath?: string): Promise<ChildNode[]> {\n const links: { node: Element; parent: ChildNode; index: number }[] = []\n\n walk(dom, (node) => {\n if ((node as Element).name !== 'link') return\n\n const el = node as Element\n const attrs = el.attribs
|
|
1
|
+
{"version":3,"file":"inlineLink.js","names":["parse"],"sources":["../../src/transformers/inlineLink.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { resolve, dirname } from 'node:path'\nimport type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize, walk } from '../utils/ast/index.ts'\n\n/**\n * Inline `<link rel=\"stylesheet\">` tags as `<style>` tags.\n *\n * - Local file paths are inlined when `filePath` is provided (resolved\n * relative to it).\n * - Remote URLs (`http://` / `https://`) are only inlined when the link\n * carries an `inline` attribute, e.g. `<link rel=\"stylesheet\" inline href=\"…\">`.\n *\n * @param html HTML string to transform.\n * @param filePath Path of the source file the HTML came from, used as the\n * base for resolving relative `href` values. Required for\n * local-file inlining; remote `inline` links work without it.\n * @returns The transformed HTML string.\n *\n * @example\n * import { inlineLink } from '@maizzle/framework'\n *\n * const out = await inlineLink(\n * '<link rel=\"stylesheet\" href=\"./styles.css\">',\n * '/path/to/template.html',\n * )\n */\nexport async function inlineLink(html: string, filePath?: string): Promise<string> {\n return serialize(await inlineLinkDom(parse(html), filePath))\n}\n\n/**\n * DOM-form of {@link inlineLink} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport async function inlineLinkDom(dom: ChildNode[], filePath?: string): Promise<ChildNode[]> {\n const links: { node: Element; parent: ChildNode; index: number }[] = []\n\n walk(dom, (node) => {\n if ((node as Element).name !== 'link') return\n\n const el = node as Element\n const attrs = el.attribs\n\n if (attrs.rel !== 'stylesheet' || !attrs.href) return\n\n const parent = el.parent as ChildNode\n\n if (parent && 'children' in parent) {\n const index = (parent.children as ChildNode[]).indexOf(el)\n if (index !== -1) {\n links.push({ node: el, parent, index })\n }\n } else {\n // Top-level node\n const index = dom.indexOf(el)\n if (index !== -1) {\n links.push({ node: el, parent: null as any, index })\n }\n }\n })\n\n for (const { node, parent, index } of links) {\n const href = node.attribs.href\n const isRemote = href.startsWith('http://') || href.startsWith('https://')\n\n let css: string | undefined\n\n if (isRemote) {\n if (!('inline' in node.attribs)) continue\n\n try {\n const response = await fetch(href)\n css = await response.text()\n } catch {\n continue\n }\n } else {\n if (!filePath) continue\n\n try {\n const absolutePath = resolve(dirname(filePath), href)\n css = readFileSync(absolutePath, 'utf8')\n } catch {\n continue\n }\n }\n\n const styleNode = {\n type: 'tag',\n name: 'style',\n attribs: {},\n children: [{\n type: 'text',\n data: css,\n parent: null as any,\n }],\n parent: parent || null,\n } as any\n\n // Set parent reference on the text child\n styleNode.children[0].parent = styleNode\n\n const siblings = parent && 'children' in parent\n ? parent.children as ChildNode[]\n : dom\n\n siblings.splice(index, 1, styleNode)\n }\n\n return dom\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,eAAsB,WAAW,MAAc,UAAoC;CACjF,OAAO,UAAU,MAAM,cAAcA,QAAM,IAAI,GAAG,QAAQ,CAAC;AAC7D;;;;;;AAOA,eAAsB,cAAc,KAAkB,UAAyC;CAC7F,MAAM,QAA+D,CAAC;CAEtE,KAAK,MAAM,SAAS;EAClB,IAAK,KAAiB,SAAS,QAAQ;EAEvC,MAAM,KAAK;EACX,MAAM,QAAQ,GAAG;EAEjB,IAAI,MAAM,QAAQ,gBAAgB,CAAC,MAAM,MAAM;EAE/C,MAAM,SAAS,GAAG;EAElB,IAAI,UAAU,cAAc,QAAQ;GAClC,MAAM,QAAS,OAAO,SAAyB,QAAQ,EAAE;GACzD,IAAI,UAAU,IACZ,MAAM,KAAK;IAAE,MAAM;IAAI;IAAQ;GAAM,CAAC;EAE1C,OAAO;GAEL,MAAM,QAAQ,IAAI,QAAQ,EAAE;GAC5B,IAAI,UAAU,IACZ,MAAM,KAAK;IAAE,MAAM;IAAI,QAAQ;IAAa;GAAM,CAAC;EAEvD;CACF,CAAC;CAED,KAAK,MAAM,EAAE,MAAM,QAAQ,WAAW,OAAO;EAC3C,MAAM,OAAO,KAAK,QAAQ;EAC1B,MAAM,WAAW,KAAK,WAAW,SAAS,KAAK,KAAK,WAAW,UAAU;EAEzE,IAAI;EAEJ,IAAI,UAAU;GACZ,IAAI,EAAE,YAAY,KAAK,UAAU;GAEjC,IAAI;IAEF,MAAM,OAAM,MADW,MAAM,IAAI,EAAA,CACZ,KAAK;GAC5B,QAAQ;IACN;GACF;EACF,OAAO;GACL,IAAI,CAAC,UAAU;GAEf,IAAI;IAEF,MAAM,aADe,QAAQ,QAAQ,QAAQ,GAAG,IAClB,GAAG,MAAM;GACzC,QAAQ;IACN;GACF;EACF;EAEA,MAAM,YAAY;GAChB,MAAM;GACN,MAAM;GACN,SAAS,CAAC;GACV,UAAU,CAAC;IACT,MAAM;IACN,MAAM;IACN,QAAQ;GACV,CAAC;GACD,QAAQ,UAAU;EACpB;EAGA,UAAU,SAAS,EAAE,CAAC,SAAS;EAM/B,CAJiB,UAAU,cAAc,SACrC,OAAO,WACP,IAAA,CAEK,OAAO,OAAO,GAAG,SAAS;CACrC;CAEA,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"minify.js","names":["merge"],"sources":["../../src/transformers/minify.ts"],"sourcesContent":["import { crush } from 'html-crush'\nimport { defu as merge } from 'defu'\nimport type { Opts as HtmlCrushOptions } from 'html-crush'\n\nexport type { Opts as MinifyOptions } from 'html-crush'\n\nconst DEFAULT_OPTIONS: Partial<HtmlCrushOptions> = {\n removeLineBreaks: true,\n}\n\n/**\n * Minify an HTML string using `html-crush`. Maizzle's only default that\n * differs from html-crush's own defaults is `removeLineBreaks: true`.\n *\n * @param html HTML string to minify.\n * @param options [html-crush options](https://codsen.com/os/html-crush) merged\n * on top of the Maizzle defaults.\n * @returns The minified HTML string.\n *\n * @example\n * import { minify } from '@maizzle/framework'\n *\n * const tight = minify('<p> hello </p>', { removeIndentations: true })\n */\nexport function minify(html: string, options: Partial<HtmlCrushOptions> = {}): string {\n const merged = merge(options, DEFAULT_OPTIONS) as Partial<HtmlCrushOptions>\n return crush(html, merged).result\n}\n"],"mappings":";;;AAMA,MAAM,kBAA6C,EACjD,kBAAkB,KACpB;;;;;;;;;;;;;;;AAgBA,SAAgB,OAAO,MAAc,UAAqC,CAAC,GAAW;CAEpF,OAAO,MAAM,MADEA,OAAM,SAAS,eACN,CAAC,
|
|
1
|
+
{"version":3,"file":"minify.js","names":["merge"],"sources":["../../src/transformers/minify.ts"],"sourcesContent":["import { crush } from 'html-crush'\nimport { defu as merge } from 'defu'\nimport type { Opts as HtmlCrushOptions } from 'html-crush'\n\nexport type { Opts as MinifyOptions } from 'html-crush'\n\nconst DEFAULT_OPTIONS: Partial<HtmlCrushOptions> = {\n removeLineBreaks: true,\n}\n\n/**\n * Minify an HTML string using `html-crush`. Maizzle's only default that\n * differs from html-crush's own defaults is `removeLineBreaks: true`.\n *\n * @param html HTML string to minify.\n * @param options [html-crush options](https://codsen.com/os/html-crush) merged\n * on top of the Maizzle defaults.\n * @returns The minified HTML string.\n *\n * @example\n * import { minify } from '@maizzle/framework'\n *\n * const tight = minify('<p> hello </p>', { removeIndentations: true })\n */\nexport function minify(html: string, options: Partial<HtmlCrushOptions> = {}): string {\n const merged = merge(options, DEFAULT_OPTIONS) as Partial<HtmlCrushOptions>\n return crush(html, merged).result\n}\n"],"mappings":";;;AAMA,MAAM,kBAA6C,EACjD,kBAAkB,KACpB;;;;;;;;;;;;;;;AAgBA,SAAgB,OAAO,MAAc,UAAqC,CAAC,GAAW;CAEpF,OAAO,MAAM,MADEA,OAAM,SAAS,eACN,CAAC,CAAC,CAAC;AAC7B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"minifyCodeInline.js","names":[],"sources":["../../src/transformers/minifyCodeInline.ts"],"sourcesContent":["/**\n * Restore HTML inside elements marked `data-minify-inline`, then strip the\n * marker attribute and trim formatter-injected whitespace.\n *\n * Named for its primary client, `<CodeInline theme=\"…\">`. The component\n * replaces shiki's structural `<`/`>` with private markers `§MZLT§` /\n * `§MZGT§` so the format pass (oxfmt with `htmlWhitespaceSensitivity:\n * 'ignore'`) can't see them as real angle brackets and reflow the\n * chain of `<span>` tokens. Source-level entities like `<` (a\n * literal `<` in the user's code) are made of `&`, `l`, `t`, `;` —\n * no real `<` — so they pass through this pipeline untouched and\n * land in the browser as entities, rendering correctly as `<`.\n *\n * Runs unconditionally near the end of the pipeline so:\n * 1. The markers always get decoded back to real `<` / `>`.\n * 2. The `data-minify-inline` attribute never leaks to final HTML\n * (whether or not the inner content had markers).\n * 3. Whitespace the formatter injected around the inner content\n * (e.g. between `<code>` and the text node) is trimmed so the\n * inline element lands flush.\n *\n * The marker attribute is intentionally generic so any component facing\n * the same formatter-vs-inline-structure problem can opt in.\n */\nexport function minifyCodeInline(html: string): string {\n if (!html.includes('data-minify-inline')) return html\n\n return html.replace(\n /<([a-zA-Z][\\w-]*)([^>]*?)\\s+data-minify-inline(?:=\"[^\"]*\")?([^>]*)>([\\s\\S]*?)<\\/\\1>/g,\n (_full, tag, before, after, contents) => {\n const cleanedAttrs = `${before}${after}`.replace(/\\s+/g, ' ').trim()\n const open = cleanedAttrs ? `<${tag} ${cleanedAttrs}>` : `<${tag}>`\n const decoded = contents\n .replace(/§MZLT§/g, '<')\n .replace(/§MZGT§/g, '>')\n .trim()\n return `${open}${decoded}</${tag}>`\n },\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,iBAAiB,MAAsB;CACrD,IAAI,CAAC,KAAK,SAAS,oBAAoB,GAAG,OAAO;CAEjD,OAAO,KAAK,QACV,yFACC,OAAO,KAAK,QAAQ,OAAO,aAAa;EACvC,MAAM,eAAe,GAAG,SAAS,QAAQ,QAAQ,QAAQ,GAAG,
|
|
1
|
+
{"version":3,"file":"minifyCodeInline.js","names":[],"sources":["../../src/transformers/minifyCodeInline.ts"],"sourcesContent":["/**\n * Restore HTML inside elements marked `data-minify-inline`, then strip the\n * marker attribute and trim formatter-injected whitespace.\n *\n * Named for its primary client, `<CodeInline theme=\"…\">`. The component\n * replaces shiki's structural `<`/`>` with private markers `§MZLT§` /\n * `§MZGT§` so the format pass (oxfmt with `htmlWhitespaceSensitivity:\n * 'ignore'`) can't see them as real angle brackets and reflow the\n * chain of `<span>` tokens. Source-level entities like `<` (a\n * literal `<` in the user's code) are made of `&`, `l`, `t`, `;` —\n * no real `<` — so they pass through this pipeline untouched and\n * land in the browser as entities, rendering correctly as `<`.\n *\n * Runs unconditionally near the end of the pipeline so:\n * 1. The markers always get decoded back to real `<` / `>`.\n * 2. The `data-minify-inline` attribute never leaks to final HTML\n * (whether or not the inner content had markers).\n * 3. Whitespace the formatter injected around the inner content\n * (e.g. between `<code>` and the text node) is trimmed so the\n * inline element lands flush.\n *\n * The marker attribute is intentionally generic so any component facing\n * the same formatter-vs-inline-structure problem can opt in.\n */\nexport function minifyCodeInline(html: string): string {\n if (!html.includes('data-minify-inline')) return html\n\n return html.replace(\n /<([a-zA-Z][\\w-]*)([^>]*?)\\s+data-minify-inline(?:=\"[^\"]*\")?([^>]*)>([\\s\\S]*?)<\\/\\1>/g,\n (_full, tag, before, after, contents) => {\n const cleanedAttrs = `${before}${after}`.replace(/\\s+/g, ' ').trim()\n const open = cleanedAttrs ? `<${tag} ${cleanedAttrs}>` : `<${tag}>`\n const decoded = contents\n .replace(/§MZLT§/g, '<')\n .replace(/§MZGT§/g, '>')\n .trim()\n return `${open}${decoded}</${tag}>`\n },\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,iBAAiB,MAAsB;CACrD,IAAI,CAAC,KAAK,SAAS,oBAAoB,GAAG,OAAO;CAEjD,OAAO,KAAK,QACV,yFACC,OAAO,KAAK,QAAQ,OAAO,aAAa;EACvC,MAAM,eAAe,GAAG,SAAS,QAAQ,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK;EAMnE,OAAO,GALM,eAAe,IAAI,IAAI,GAAG,aAAa,KAAK,IAAI,IAAI,KACjD,SACb,QAAQ,WAAW,GAAG,CAAC,CACvB,QAAQ,WAAW,GAAG,CAAC,CACvB,KACoB,EAAE,IAAI,IAAI;CACnC,CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"msoPlaceholders.d.ts","names":[],"sources":["../../src/transformers/msoPlaceholders.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"msoPlaceholders.d.ts","names":[],"sources":["../../src/transformers/msoPlaceholders.ts"],"mappings":";;;;;AA8CA;;;;;;;;AAA4D;;;;;;;;;;;;;;;;iBAA5C,eAAA,CAAgB,GAAA,EAAK,SAAA,KAAc,SAAS"}
|
|
@@ -12,13 +12,8 @@ function resolveWidth(value) {
|
|
|
12
12
|
const m = trimmed.match(/^([\d.]+)(px|rem|em|pt)?$/i);
|
|
13
13
|
if (!m) return null;
|
|
14
14
|
const n = parseFloat(m[1]);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
case "rem":
|
|
18
|
-
case "em": return `${Math.round(n * 16)}px`;
|
|
19
|
-
case "pt": return `${Math.round(n * 1.333)}px`;
|
|
20
|
-
default: return null;
|
|
21
|
-
}
|
|
15
|
+
const unit = (m[2] || "px").toLowerCase();
|
|
16
|
+
return `${Math.round(n * (unit === "rem" || unit === "em" ? 16 : unit === "pt" ? 1.333 : 1))}px`;
|
|
22
17
|
}
|
|
23
18
|
/**
|
|
24
19
|
* Resolve all `__MAIZZLE_MSO*__` placeholders inside MSO conditional comments
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"msoPlaceholders.js","names":["el"],"sources":["../../src/transformers/msoPlaceholders.ts"],"sourcesContent":["import safeParser from 'postcss-safe-parser'\nimport { walk } from '../utils/ast/index.ts'\nimport { horizontalBorderPx } from '../utils/cssBox.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
|
|
1
|
+
{"version":3,"file":"msoPlaceholders.js","names":["el"],"sources":["../../src/transformers/msoPlaceholders.ts"],"sourcesContent":["import safeParser from 'postcss-safe-parser'\nimport { walk } from '../utils/ast/index.ts'\nimport { horizontalBorderPx } from '../utils/cssBox.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 const unit = (m[2] || 'px').toLowerCase()\n const factor = unit === 'rem' || unit === 'em' ? 16 : unit === 'pt' ? 1.333 : 1\n return `${Math.round(n * factor)}px`\n}\n\n/**\n * Resolve all `__MAIZZLE_MSO*__` placeholders inside MSO conditional comments\n * by reading inlined style + `data-*` markers on the paired elements.\n *\n * Two placeholder families:\n *\n * MSOW (`__MAIZZLE_MSOW_{id}__`) — emitted by `<Container>` and `<Section>`.\n * Source element is marked with `data-maizzle-msow-id`. Reads inlined\n * `max-width:` (falls back to `width:`) and normalizes to px. Falls\n * back to `data-maizzle-msow-fallback` (default `600px`) when the\n * value can't be parsed.\n *\n * MSOTDSTYLE (`__MAIZZLE_MSOTDSTYLE_{id}__`) — emitted by `<Container>` and\n * `<Section>`'s MSO `<td>`. Source element is marked with\n * `data-maizzle-mso-td-id`. Extracts from the inlined style:\n * - `background-color` (always, when present) so Word paints the cell.\n * - `padding*` (only when no horizontal border on the element, since\n * Word drops div padding without a border and a copy would\n * double-pad with one).\n * Appends the `data-maizzle-mso-style` value (the user's `msoStyle`\n * prop) last so it wins on duplicates. Empty input resolves to ''\n * so the placeholder collapses cleanly.\n *\n * Single collect-walk + single substitute-walk: the same Container div\n * carries both marker kinds, so one element visit fills both maps.\n */\nexport function msoPlaceholders(dom: ChildNode[]): ChildNode[] {\n const widths = new Map<string, string>()\n const tdStyles = new Map<string, string>()\n\n walk(dom, (node) => {\n const el = node as Element\n const a = el.attribs\n if (!a) return\n\n const msowId = a['data-maizzle-msow-id']\n const tdId = a['data-maizzle-mso-td-id']\n if (!msowId && !tdId) return\n\n const style = a.style ?? ''\n\n if (msowId) {\n delete a['data-maizzle-msow-id']\n const fallback = a['data-maizzle-msow-fallback'] ?? '600px'\n delete a['data-maizzle-msow-fallback']\n const raw = style.match(RE_MAX_WIDTH)?.[1] ?? style.match(RE_WIDTH)?.[1]\n const resolved = raw ? resolveWidth(raw) : null\n widths.set(msowId, resolved ?? fallback)\n }\n\n if (tdId) {\n delete a['data-maizzle-mso-td-id']\n const msoStyle = (a['data-maizzle-mso-style'] ?? '').trim().replace(/;\\s*$/, '')\n delete a['data-maizzle-mso-style']\n\n /**\n * Build the MSO td's inline style from three sources, in CSS priority order\n * (earlier = lower, later wins on dupes):\n *\n * 1. `background-color` (always, when present) — Word paints the cell\n * under any padding area or inline-block gap, not just the div.\n *\n * 2. `padding*` (hoisted only when no horizontal border) — Word drops\n * div padding without a stabilizing border, so the td has to\n * carry it. With a border, Word renders div padding and a td\n * copy would double-pad.\n *\n * 3. The user's `mso-style` prop — last so it overrides anything the\n * auto-hoist computed.\n */\n const parts: string[] = []\n if (style) {\n const root = safeParser(style)\n\n let bgDecl: string | undefined\n root.walkDecls('background-color', (d) => {\n bgDecl = `background-color: ${d.value}${d.important ? ' !important' : ''}`\n })\n if (bgDecl) parts.push(bgDecl)\n\n if (horizontalBorderPx(root) === 0) {\n root.walkDecls((d) => {\n if (/^padding(-|$)/.test(d.prop)) {\n parts.push(`${d.prop}: ${d.value}${d.important ? ' !important' : ''}`)\n }\n })\n }\n }\n if (msoStyle) parts.push(msoStyle)\n\n tdStyles.set(tdId, parts.length ? ` style=\"${parts.join('; ')}\"` : '')\n }\n })\n\n if (widths.size === 0 && tdStyles.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) return\n const hasMsow = widths.size > 0 && data.includes('__MAIZZLE_MSOW_')\n const hasTd = tdStyles.size > 0 && data.includes('__MAIZZLE_MSOTDSTYLE_')\n if (!hasMsow && !hasTd) return\n\n if (hasMsow) {\n for (const [id, val] of widths) {\n data = data.replaceAll(`__MAIZZLE_MSOW_${id}__`, val)\n }\n }\n if (hasTd) {\n for (const [id, val] of tdStyles) {\n data = data.replaceAll(`__MAIZZLE_MSOTDSTYLE_${id}__`, val)\n }\n }\n ;(node as any).data = data\n })\n\n return dom\n}\n"],"mappings":";;;;;AAKA,MAAM,eAAe;AACrB,MAAM,WAAW;AACjB,MAAM,aAAa;AAEnB,SAAS,aAAa,OAA8B;CAClD,MAAM,UAAU,MAAM,KAAK;CAC3B,IAAI,WAAW,KAAK,OAAO,GAAG,OAAO;CACrC,MAAM,IAAI,QAAQ,MAAM,4BAA4B;CACpD,IAAI,CAAC,GAAG,OAAO;CACf,MAAM,IAAI,WAAW,EAAE,EAAE;CACzB,MAAM,QAAQ,EAAE,MAAM,KAAA,CAAM,YAAY;CAExC,OAAO,GAAG,KAAK,MAAM,KADN,SAAS,SAAS,SAAS,OAAO,KAAK,SAAS,OAAO,QAAQ,EAC/C,EAAE;AACnC;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,gBAAgB,KAA+B;CAC7D,MAAM,yBAAS,IAAI,IAAoB;CACvC,MAAM,2BAAW,IAAI,IAAoB;CAEzC,KAAK,MAAM,SAAS;EAElB,MAAM,IAAIA,KAAG;EACb,IAAI,CAAC,GAAG;EAER,MAAM,SAAS,EAAE;EACjB,MAAM,OAAO,EAAE;EACf,IAAI,CAAC,UAAU,CAAC,MAAM;EAEtB,MAAM,QAAQ,EAAE,SAAS;EAEzB,IAAI,QAAQ;GACV,OAAO,EAAE;GACT,MAAM,WAAW,EAAE,iCAAiC;GACpD,OAAO,EAAE;GACT,MAAM,MAAM,MAAM,MAAM,YAAY,CAAC,GAAG,MAAM,MAAM,MAAM,QAAQ,CAAC,GAAG;GACtE,MAAM,WAAW,MAAM,aAAa,GAAG,IAAI;GAC3C,OAAO,IAAI,QAAQ,YAAY,QAAQ;EACzC;EAEA,IAAI,MAAM;GACR,OAAO,EAAE;GACT,MAAM,YAAY,EAAE,6BAA6B,GAAA,CAAI,KAAK,CAAC,CAAC,QAAQ,SAAS,EAAE;GAC/E,OAAO,EAAE;;;;;;;;;;;;;;;;GAiBT,MAAM,QAAkB,CAAC;GACzB,IAAI,OAAO;IACT,MAAM,OAAO,WAAW,KAAK;IAE7B,IAAI;IACJ,KAAK,UAAU,qBAAqB,MAAM;KACxC,SAAS,qBAAqB,EAAE,QAAQ,EAAE,YAAY,gBAAgB;IACxE,CAAC;IACD,IAAI,QAAQ,MAAM,KAAK,MAAM;IAE7B,IAAI,mBAAmB,IAAI,MAAM,GAC/B,KAAK,WAAW,MAAM;KACpB,IAAI,gBAAgB,KAAK,EAAE,IAAI,GAC7B,MAAM,KAAK,GAAG,EAAE,KAAK,IAAI,EAAE,QAAQ,EAAE,YAAY,gBAAgB,IAAI;IAEzE,CAAC;GAEL;GACA,IAAI,UAAU,MAAM,KAAK,QAAQ;GAEjC,SAAS,IAAI,MAAM,MAAM,SAAS,WAAW,MAAM,KAAK,IAAI,EAAE,KAAK,EAAE;EACvE;CACF,CAAC;CAED,IAAI,OAAO,SAAS,KAAK,SAAS,SAAS,GAAG,OAAO;CAErD,KAAK,MAAM,SAAS;EAClB,IAAI,KAAK,SAAS,WAAW;EAC7B,IAAI,OAAQ,KAAa;EACzB,IAAI,CAAC,MAAM;EACX,MAAM,UAAU,OAAO,OAAO,KAAK,KAAK,SAAS,iBAAiB;EAClE,MAAM,QAAQ,SAAS,OAAO,KAAK,KAAK,SAAS,uBAAuB;EACxE,IAAI,CAAC,WAAW,CAAC,OAAO;EAExB,IAAI,SACF,KAAK,MAAM,CAAC,IAAI,QAAQ,QACtB,OAAO,KAAK,WAAW,kBAAkB,GAAG,KAAK,GAAG;EAGxD,IAAI,OACF,KAAK,MAAM,CAAC,IAAI,QAAQ,UACtB,OAAO,KAAK,WAAW,wBAAwB,GAAG,KAAK,GAAG;EAG7D,KAAc,OAAO;CACxB,CAAC;CAED,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"purgeCss.js","names":["merge"],"sources":["../../src/transformers/purgeCss.ts"],"sourcesContent":["import { comb } from 'email-comb'\nimport { defu as merge } from 'defu'\nimport safeParser from 'postcss-safe-parser'\nimport { selectAll } from 'css-select'\nimport type { ChildNode, Element } from 'domhandler'\nimport type { Opts as CombOptions } from 'email-comb'\nimport { parse, serialize, walk } from '../utils/ast/index.ts'\n\nconst DEFAULT_SAFELIST: string[] = [\n '*body*', // Gmail\n '.gmail*', // Gmail\n '.apple*', // Apple Mail\n '.ios*', // Mail on iOS\n '.ox-*', // Open-Xchange\n '.outlook*', // Outlook.com\n '[data-ogs*', // Outlook.com\n '.bloop_container', // Airmail\n '.Singleton', // Apple Mail 10\n '.unused', // Notes 8\n '.moz-text-html', // Thunderbird\n '.mail-detail-content', // Comcast, Libero webmail\n '*edo*', // Edison (all)\n '#*', // Freenet uses #msgBody\n '.\\\\&*', // Yahoo Mail wraps content in a class literally named \"&\"\n '.lang*', // Fenced code blocks\n]\n\nconst DEFAULT_OPTIONS = {\n backend: [\n { heads: '{{', tails: '}}' },\n { heads: '{%', tails: '%}' },\n ],\n whitelist: [...DEFAULT_SAFELIST],\n}\n\n/**\n * Options for the `purgeCss` transformer.\n */\nexport interface PurgeCssOptions extends Partial<Omit<CombOptions, 'whitelist'>> {\n /**\n * Selectors to preserve regardless of whether they're matched in the\n * markup. Appended to Maizzle's built-in safelist (Gmail, Apple Mail,\n * Outlook.com hooks, etc). Mapped to email-comb's `whitelist` option.\n */\n safelist?: string[]\n}\n\n/**\n * Remove unused CSS from an HTML string.\n *\n * Uses `email-comb` together with a DOM-aware deep-purge step to strip\n * CSS selectors and class/id references that are not matched anywhere\n * in the document body.\n *\n * @param html HTML string to transform.\n * @param options Email-comb options plus a Maizzle `safelist`.\n * @returns The transformed HTML string.\n *\n * @example\n * import { purgeCss } from '@maizzle/framework'\n *\n * const out = purgeCss('<style>.a{}.b{}</style><p class=\"a\">x</p>', {\n * safelist: ['.keep'],\n * })\n */\nexport function purgeCss(html: string, options: PurgeCssOptions = {}): string {\n return serialize(purgeCssDom(parse(html), options))\n}\n\n/**\n * DOM-form of {@link purgeCss} used by the internal transformer\n * pipeline. Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function purgeCssDom(dom: ChildNode[], options: PurgeCssOptions = {}): ChildNode[] {\n const userSafelist = Array.isArray(options.safelist) ? options.safelist : []\n\n const { safelist: _discard, ...restUserOptions } = options\n\n /**\n * Merge user options on top of defaults. defu merges objects deeply;\n * for arrays it appends user values. We want the user safelist\n * appended to the default safelist, so we build whitelist\n * manually.\n */\n const combOptions = merge(\n { ...restUserOptions, whitelist: [...DEFAULT_SAFELIST, ...userSafelist] },\n DEFAULT_OPTIONS,\n )\n\n /**\n * Deep purge first: DOM-aware selector removal via PostCSS + css-select.\n * Runs before email-comb so that email-comb can clean up orphaned\n * classes in HTML attributes left behind by removed CSS rules.\n */\n const safelist = [...DEFAULT_SAFELIST, ...userSafelist]\n dom = deepPurge(dom, safelist)\n\n /**\n * Shield embed style tags from email-comb. Comb has no skip option,\n * so it strips CSS comments and drops class refs it can't match\n * against visible CSS. Swap each embed tag's body for a unique\n * stub rule (`.maizzle-keep-N{}`) so comb keeps the tag, then\n * whitelist that stub plus every selector from the original\n * CSS so comb leaves matching refs alone elsewhere — and\n * finally restore the original CSS once comb has run.\n */\n const stash: { token: string; original: string; textNode: any }[] = []\n const extraWhitelist: string[] = []\n walk(dom, (node) => {\n const el = node as Element\n if (el.name !== 'style' || !el.attribs) return\n if (!('embed' in el.attribs) && !('data-embed' in el.attribs) && !('amp-custom' in el.attribs)) return\n const textNode = el.children?.find((c: any) => c.type === 'text') as any\n if (!textNode?.data) return\n const idx = stash.length\n const token = `.maizzle-keep-${idx}`\n extraWhitelist.push(token)\n for (const m of textNode.data.matchAll(/(?<![\\w-])[.#][a-zA-Z_][\\w-]*/g)) {\n extraWhitelist.push(m[0])\n }\n stash.push({ token, original: textNode.data, textNode })\n textNode.data = `${token}{}`\n })\n\n if (extraWhitelist.length) {\n combOptions.whitelist = [...(combOptions.whitelist as string[] ?? []), ...extraWhitelist]\n }\n\n const { result } = comb(serialize(dom), combOptions)\n\n /**\n * Comb returns a fresh string, so we work off the post-parse tree:\n * find each embed style tag whose body still starts with the stub\n * token we planted earlier and swap the original CSS back in.\n */\n let purgedDom = parse(result)\n\n if (stash.length) {\n walk(purgedDom, (node) => {\n const el = node as Element\n if (el.name !== 'style' || !el.attribs) return\n if (!('embed' in el.attribs) && !('data-embed' in el.attribs) && !('amp-custom' in el.attribs)) return\n const textNode = el.children?.find((c: any) => c.type === 'text') as any\n if (!textNode?.data) return\n const trimmed = textNode.data.trim()\n const match = stash.find(s => trimmed === `${s.token}{}` || trimmed.startsWith(`${s.token}{`))\n if (match) textNode.data = match.original\n })\n }\n\n /**\n * Clean up data-embed/embed attributes — no longer needed after purging.\n * `amp-custom` stays as-is (it's the user-authored AMP4Email attr).\n */\n walk(purgedDom, (node) => {\n const el = node as Element\n if (el.name === 'style' && el.attribs) {\n delete el.attribs['data-embed']\n delete el.attribs.embed\n }\n })\n\n return purgedDom\n}\n\n/**\n * Deep purge: uses PostCSS to parse CSS in non-embedded style tags,\n * then checks each selector against the DOM with css-select.\n * Removes rules where no selector matches any element.\n */\nfunction isSafelisted(selector: string, safelist: string[]): boolean {\n return safelist.some((pattern) => {\n if (pattern.startsWith('*') && pattern.endsWith('*')) {\n return selector.includes(pattern.slice(1, -1))\n }\n if (pattern.endsWith('*')) {\n return selector.startsWith(pattern.slice(0, -1))\n }\n if (pattern.startsWith('*')) {\n return selector.endsWith(pattern.slice(1))\n }\n return selector === pattern\n })\n}\n\nfunction deepPurge(dom: ChildNode[], safelist: string[]): ChildNode[] {\n walk(dom, (node) => {\n const el = node as Element\n\n if (el.name !== 'style' || !el.attribs) return\n if ('data-embed' in el.attribs || 'embed' in el.attribs || 'amp-custom' in el.attribs) return\n\n const textNode = el.children?.find((c: any) => c.type === 'text') as any\n if (!textNode?.data?.trim()) return\n\n const root = safeParser(textNode.data)\n\n root.walkRules((rule) => {\n /**\n * Skip rules inside @media or other at-rules — those may target\n * states we can't match statically (hover, responsive, etc.).\n */\n if (rule.parent?.type === 'atrule') return\n\n const selectors = rule.selectors ?? [rule.selector]\n const matched = selectors.filter((sel) => {\n // Keep safelisted selectors\n if (isSafelisted(sel, safelist)) return true\n\n /**\n * Skip pseudo-classes/elements that can't be matched statically.\n * Functional pseudos like :not(), :is(), :where(), :has() are\n * matchable by css-select, so we only skip dynamic/state\n * ones.\n */\n if (/::[\\w-]/.test(sel)) return true\n if (/(?<!:):(?!not\\b|is\\b|where\\b|has\\b)[\\w-]/.test(sel.replace(/\\\\./g, ''))) return true\n\n try {\n return selectAll(sel, dom).length > 0\n } catch {\n // If css-select can't parse the selector, keep it\n return true\n }\n })\n\n if (matched.length === 0) {\n rule.remove()\n } else if (matched.length < selectors.length) {\n rule.selectors = matched\n }\n })\n\n // Remove empty at-rules\n root.walkAtRules((atRule) => {\n if (atRule.nodes?.length === 0) {\n atRule.remove()\n }\n })\n\n const purgedCss = root.toString()\n\n if (purgedCss.trim()) {\n textNode.data = purgedCss\n } else {\n // Remove the style tag entirely if empty\n const parent = el.parent\n if (parent && 'children' in parent) {\n const idx = parent.children.indexOf(el as any)\n if (idx !== -1) parent.children.splice(idx, 1)\n }\n }\n })\n\n return dom\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"purgeCss.js","names":["merge"],"sources":["../../src/transformers/purgeCss.ts"],"sourcesContent":["import { comb } from 'email-comb'\nimport { defu as merge } from 'defu'\nimport safeParser from 'postcss-safe-parser'\nimport { selectAll } from 'css-select'\nimport type { ChildNode, Element } from 'domhandler'\nimport type { Opts as CombOptions } from 'email-comb'\nimport { parse, serialize, walk } from '../utils/ast/index.ts'\n\nconst DEFAULT_SAFELIST: string[] = [\n '*body*', // Gmail\n '.gmail*', // Gmail\n '.apple*', // Apple Mail\n '.ios*', // Mail on iOS\n '.ox-*', // Open-Xchange\n '.outlook*', // Outlook.com\n '[data-ogs*', // Outlook.com\n '.bloop_container', // Airmail\n '.Singleton', // Apple Mail 10\n '.unused', // Notes 8\n '.moz-text-html', // Thunderbird\n '.mail-detail-content', // Comcast, Libero webmail\n '*edo*', // Edison (all)\n '#*', // Freenet uses #msgBody\n '.\\\\&*', // Yahoo Mail wraps content in a class literally named \"&\"\n '.lang*', // Fenced code blocks\n]\n\nconst DEFAULT_OPTIONS = {\n backend: [\n { heads: '{{', tails: '}}' },\n { heads: '{%', tails: '%}' },\n ],\n whitelist: [...DEFAULT_SAFELIST],\n}\n\n/**\n * Options for the `purgeCss` transformer.\n */\nexport interface PurgeCssOptions extends Partial<Omit<CombOptions, 'whitelist'>> {\n /**\n * Selectors to preserve regardless of whether they're matched in the\n * markup. Appended to Maizzle's built-in safelist (Gmail, Apple Mail,\n * Outlook.com hooks, etc). Mapped to email-comb's `whitelist` option.\n */\n safelist?: string[]\n}\n\n/**\n * Remove unused CSS from an HTML string.\n *\n * Uses `email-comb` together with a DOM-aware deep-purge step to strip\n * CSS selectors and class/id references that are not matched anywhere\n * in the document body.\n *\n * @param html HTML string to transform.\n * @param options Email-comb options plus a Maizzle `safelist`.\n * @returns The transformed HTML string.\n *\n * @example\n * import { purgeCss } from '@maizzle/framework'\n *\n * const out = purgeCss('<style>.a{}.b{}</style><p class=\"a\">x</p>', {\n * safelist: ['.keep'],\n * })\n */\nexport function purgeCss(html: string, options: PurgeCssOptions = {}): string {\n return serialize(purgeCssDom(parse(html), options))\n}\n\n/**\n * DOM-form of {@link purgeCss} used by the internal transformer\n * pipeline. Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function purgeCssDom(dom: ChildNode[], options: PurgeCssOptions = {}): ChildNode[] {\n const userSafelist = Array.isArray(options.safelist) ? options.safelist : []\n\n const { safelist: _discard, ...restUserOptions } = options\n\n /**\n * Merge user options on top of defaults. defu merges objects deeply;\n * for arrays it appends user values. We want the user safelist\n * appended to the default safelist, so we build whitelist\n * manually.\n */\n const combOptions = merge(\n { ...restUserOptions, whitelist: [...DEFAULT_SAFELIST, ...userSafelist] },\n DEFAULT_OPTIONS,\n )\n\n /**\n * Deep purge first: DOM-aware selector removal via PostCSS + css-select.\n * Runs before email-comb so that email-comb can clean up orphaned\n * classes in HTML attributes left behind by removed CSS rules.\n */\n const safelist = [...DEFAULT_SAFELIST, ...userSafelist]\n dom = deepPurge(dom, safelist)\n\n /**\n * Shield embed style tags from email-comb. Comb has no skip option,\n * so it strips CSS comments and drops class refs it can't match\n * against visible CSS. Swap each embed tag's body for a unique\n * stub rule (`.maizzle-keep-N{}`) so comb keeps the tag, then\n * whitelist that stub plus every selector from the original\n * CSS so comb leaves matching refs alone elsewhere — and\n * finally restore the original CSS once comb has run.\n */\n const stash: { token: string; original: string; textNode: any }[] = []\n const extraWhitelist: string[] = []\n walk(dom, (node) => {\n const el = node as Element\n if (el.name !== 'style' || !el.attribs) return\n if (!('embed' in el.attribs) && !('data-embed' in el.attribs) && !('amp-custom' in el.attribs)) return\n const textNode = el.children?.find((c: any) => c.type === 'text') as any\n if (!textNode?.data) return\n const idx = stash.length\n const token = `.maizzle-keep-${idx}`\n extraWhitelist.push(token)\n for (const m of textNode.data.matchAll(/(?<![\\w-])[.#][a-zA-Z_][\\w-]*/g)) {\n extraWhitelist.push(m[0])\n }\n stash.push({ token, original: textNode.data, textNode })\n textNode.data = `${token}{}`\n })\n\n if (extraWhitelist.length) {\n combOptions.whitelist = [...(combOptions.whitelist as string[] ?? []), ...extraWhitelist]\n }\n\n const { result } = comb(serialize(dom), combOptions)\n\n /**\n * Comb returns a fresh string, so we work off the post-parse tree:\n * find each embed style tag whose body still starts with the stub\n * token we planted earlier and swap the original CSS back in.\n */\n let purgedDom = parse(result)\n\n if (stash.length) {\n walk(purgedDom, (node) => {\n const el = node as Element\n if (el.name !== 'style' || !el.attribs) return\n if (!('embed' in el.attribs) && !('data-embed' in el.attribs) && !('amp-custom' in el.attribs)) return\n const textNode = el.children?.find((c: any) => c.type === 'text') as any\n if (!textNode?.data) return\n const trimmed = textNode.data.trim()\n const match = stash.find(s => trimmed === `${s.token}{}` || trimmed.startsWith(`${s.token}{`))\n if (match) textNode.data = match.original\n })\n }\n\n /**\n * Clean up data-embed/embed attributes — no longer needed after purging.\n * `amp-custom` stays as-is (it's the user-authored AMP4Email attr).\n */\n walk(purgedDom, (node) => {\n const el = node as Element\n if (el.name === 'style' && el.attribs) {\n delete el.attribs['data-embed']\n delete el.attribs.embed\n }\n })\n\n return purgedDom\n}\n\n/**\n * Deep purge: uses PostCSS to parse CSS in non-embedded style tags,\n * then checks each selector against the DOM with css-select.\n * Removes rules where no selector matches any element.\n */\nfunction isSafelisted(selector: string, safelist: string[]): boolean {\n return safelist.some((pattern) => {\n if (pattern.startsWith('*') && pattern.endsWith('*')) {\n return selector.includes(pattern.slice(1, -1))\n }\n if (pattern.endsWith('*')) {\n return selector.startsWith(pattern.slice(0, -1))\n }\n if (pattern.startsWith('*')) {\n return selector.endsWith(pattern.slice(1))\n }\n return selector === pattern\n })\n}\n\nfunction deepPurge(dom: ChildNode[], safelist: string[]): ChildNode[] {\n walk(dom, (node) => {\n const el = node as Element\n\n if (el.name !== 'style' || !el.attribs) return\n if ('data-embed' in el.attribs || 'embed' in el.attribs || 'amp-custom' in el.attribs) return\n\n const textNode = el.children?.find((c: any) => c.type === 'text') as any\n if (!textNode?.data?.trim()) return\n\n const root = safeParser(textNode.data)\n\n root.walkRules((rule) => {\n /**\n * Skip rules inside @media or other at-rules — those may target\n * states we can't match statically (hover, responsive, etc.).\n */\n if (rule.parent?.type === 'atrule') return\n\n const selectors = rule.selectors ?? [rule.selector]\n const matched = selectors.filter((sel) => {\n // Keep safelisted selectors\n if (isSafelisted(sel, safelist)) return true\n\n /**\n * Skip pseudo-classes/elements that can't be matched statically.\n * Functional pseudos like :not(), :is(), :where(), :has() are\n * matchable by css-select, so we only skip dynamic/state\n * ones.\n */\n if (/::[\\w-]/.test(sel)) return true\n if (/(?<!:):(?!not\\b|is\\b|where\\b|has\\b)[\\w-]/.test(sel.replace(/\\\\./g, ''))) return true\n\n try {\n return selectAll(sel, dom).length > 0\n } catch {\n // If css-select can't parse the selector, keep it\n return true\n }\n })\n\n if (matched.length === 0) {\n rule.remove()\n } else if (matched.length < selectors.length) {\n rule.selectors = matched\n }\n })\n\n // Remove empty at-rules\n root.walkAtRules((atRule) => {\n if (atRule.nodes?.length === 0) {\n atRule.remove()\n }\n })\n\n const purgedCss = root.toString()\n\n if (purgedCss.trim()) {\n textNode.data = purgedCss\n } else {\n // Remove the style tag entirely if empty\n const parent = el.parent\n if (parent && 'children' in parent) {\n const idx = parent.children.indexOf(el as any)\n if (idx !== -1) parent.children.splice(idx, 1)\n }\n }\n })\n\n return dom\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replaceStrings.js","names":[],"sources":["../../src/transformers/replaceStrings.ts"],"sourcesContent":["import type { MaizzleConfig } from '../types/config.ts'\n\n/**\n * Replace strings transformer.\n *\n * Replaces strings in the HTML using the key-value pairs defined in\n * `config.replaceStrings`. Each key is treated as a regular expression\n * pattern (case-insensitive, global), and the value is the replacement.\n *\n * Character classes must be escaped in keys, e.g. `\\\\s` for `\\s`.\n */\nexport function replaceStrings(html: string, config: MaizzleConfig = {}): string {\n const replacements = config.replaceStrings\n\n if (!replacements || Object.keys(replacements).length === 0) return html\n\n return Object.entries(replacements).reduce(\n (result, [pattern, replacement]) => result.replace(new RegExp(pattern, 'gi'), replacement),\n html,\n )\n}\n"],"mappings":";;;;;;;;;;AAWA,SAAgB,eAAe,MAAc,SAAwB,CAAC,GAAW;CAC/E,MAAM,eAAe,OAAO;CAE5B,IAAI,CAAC,gBAAgB,OAAO,KAAK,YAAY,
|
|
1
|
+
{"version":3,"file":"replaceStrings.js","names":[],"sources":["../../src/transformers/replaceStrings.ts"],"sourcesContent":["import type { MaizzleConfig } from '../types/config.ts'\n\n/**\n * Replace strings transformer.\n *\n * Replaces strings in the HTML using the key-value pairs defined in\n * `config.replaceStrings`. Each key is treated as a regular expression\n * pattern (case-insensitive, global), and the value is the replacement.\n *\n * Character classes must be escaped in keys, e.g. `\\\\s` for `\\s`.\n */\nexport function replaceStrings(html: string, config: MaizzleConfig = {}): string {\n const replacements = config.replaceStrings\n\n if (!replacements || Object.keys(replacements).length === 0) return html\n\n return Object.entries(replacements).reduce(\n (result, [pattern, replacement]) => result.replace(new RegExp(pattern, 'gi'), replacement),\n html,\n )\n}\n"],"mappings":";;;;;;;;;;AAWA,SAAgB,eAAe,MAAc,SAAwB,CAAC,GAAW;CAC/E,MAAM,eAAe,OAAO;CAE5B,IAAI,CAAC,gBAAgB,OAAO,KAAK,YAAY,CAAC,CAAC,WAAW,GAAG,OAAO;CAEpE,OAAO,OAAO,QAAQ,YAAY,CAAC,CAAC,QACjC,QAAQ,CAAC,SAAS,iBAAiB,OAAO,QAAQ,IAAI,OAAO,SAAS,IAAI,GAAG,WAAW,GACzF,IACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safeSelectors.js","names":[],"sources":["../../src/transformers/safeSelectors.ts"],"sourcesContent":["import postcss from 'postcss'\nimport safeParser from 'postcss-safe-parser'\nimport type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize, walk } from '../utils/ast/index.ts'\nimport type { CssConfig } from '../types/config.ts'\n\nconst DEFAULT_REPLACEMENTS: Record<string, string> = {\n ':': '-',\n '/': '-',\n '%': 'pc',\n '.': '_',\n ',': '_',\n '#': '_',\n '[': '',\n ']': '',\n '(': '',\n ')': '',\n '{': '',\n '}': '',\n '!': '-i',\n '&': 'and-',\n '<': 'lt-',\n '=': 'eq-',\n '>': 'gt-',\n '|': 'or-',\n '@': 'at-',\n '?': 'q-',\n '\\\\': '-',\n '\"': '-',\n \"'\": '-',\n '*': '-',\n '+': '-',\n ';': '-',\n '^': '-',\n '`': '-',\n '~': '-',\n '$': '-',\n}\n\nfunction escapeForRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * Replace escaped special characters in CSS selectors.\n *\n * Tailwind generates selectors like `.sm\\:text-base`. This function\n * replaces the `\\:` with `-` (or whatever the configured replacement is)\n * so the selector becomes `.sm-text-base`, which is safe for email clients.\n */\nfunction processCssSelectors(css: string, replacements: Record<string, string>): string {\n // Matches \\<char> in CSS selectors — e.g. \\: \\/ \\. \\[ etc.\n const selectorRegex = new RegExp(\n `\\\\\\\\(${Object.keys(replacements).map(escapeForRegex).join('|')})`,\n 'g',\n )\n\n return postcss([\n (root: postcss.Root) => {\n root.walkRules((rule: postcss.Rule) => {\n rule.selector = rule.selector\n .replace(selectorRegex, (matched, char, offset, str) => {\n /**\n * Yahoo Mail wraps content in a class literally named `&`, so\n * the selector `.\\&` must be preserved. Detect it as a\n * `\\&` that follows a `.` and ends the class atom\n * (space, combinator, comma, `{`, or end-of-str).\n */\n if (char === '&' && str[offset - 1] === '.') {\n const next = str[offset + 2]\n if (next === undefined || /[\\s,{>~+)]/.test(next)) return matched\n }\n return replacements[char] ?? matched\n })\n // Handle CSS unicode escape for comma (\\2c → _)\n .replaceAll('\\\\2c ', '_')\n })\n },\n ]).process(css, { parser: safeParser }).css\n}\n\n/**\n * Replace unsafe special characters in a class attribute value.\n *\n * Splits on whitespace and replaces each char from the replacements map\n * in each class token individually.\n */\nfunction processClassAttr(classStr: string, replacements: Record<string, string>): string {\n return classStr\n .split(/\\s+/)\n .filter(Boolean)\n .map((cls) => {\n for (const [from, to] of Object.entries(replacements)) {\n cls = cls.split(from).join(to)\n }\n return cls\n })\n .join(' ')\n}\n\n/**\n * Safe selectors transformer.\n *\n * Replaces unsafe characters (`:`, `/`, `[`, `]`, etc.) in:\n * - CSS selectors inside `<style>` tags\n * - HTML `class` attributes\n *\n * This makes Tailwind utility classes like `sm:text-base` safe for\n * email clients that cannot handle escaped characters in class names.\n *\n * Enabled by default. Disable by setting `css.safe` to `false`.\n * Customize replacements by passing a `Record<string, string>` — user\n * values are merged on top of the defaults.\n *\n * @param html HTML string to transform.\n * @param config CSS config (see {@link CssConfig}).\n * @returns The transformed HTML string.\n *\n * @example\n * import { safeSelectors } from '@maizzle/framework'\n *\n * const out = safeSelectors('<div class=\"sm:text-base\"></div>')\n */\nexport function safeSelectors(html: string, config: CssConfig = {}): string {\n return serialize(safeSelectorsDom(parse(html), config))\n}\n\n/**\n * DOM-form of {@link safeSelectors} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function safeSelectorsDom(dom: ChildNode[], config: CssConfig = {}): ChildNode[] {\n const option = config.safe ?? true\n\n if (!option) return dom\n\n const replacements: Record<string, string> =\n option && typeof option === 'object'\n ? { ...DEFAULT_REPLACEMENTS, ...option }\n : DEFAULT_REPLACEMENTS\n\n walk(dom, (node) => {\n const el = node as Element\n\n // Process CSS selectors inside <style> tags\n if (el.name === 'style' && el.children?.length) {\n const text = el.children.find((c) => c.type === 'text') as any\n if (text?.data?.trim()) {\n text.data = processCssSelectors(text.data, replacements)\n }\n }\n\n // Replace special chars in class attributes\n if ('attribs' in el && el.attribs?.class) {\n el.attribs.class = processClassAttr(el.attribs.class, replacements)\n }\n })\n\n return dom\n}\n"],"mappings":";;;;;;;AAMA,MAAM,uBAA+C;CACnD,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,MAAM;CACN,MAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;AACP;AAEA,SAAS,eAAe,GAAmB;CACzC,OAAO,EAAE,QAAQ,uBAAuB,MAAM;AAChD;;;;;;;;AASA,SAAS,oBAAoB,KAAa,cAA8C;CAEtF,MAAM,gBAAgB,IAAI,OACxB,QAAQ,OAAO,KAAK,YAAY,
|
|
1
|
+
{"version":3,"file":"safeSelectors.js","names":[],"sources":["../../src/transformers/safeSelectors.ts"],"sourcesContent":["import postcss from 'postcss'\nimport safeParser from 'postcss-safe-parser'\nimport type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize, walk } from '../utils/ast/index.ts'\nimport type { CssConfig } from '../types/config.ts'\n\nconst DEFAULT_REPLACEMENTS: Record<string, string> = {\n ':': '-',\n '/': '-',\n '%': 'pc',\n '.': '_',\n ',': '_',\n '#': '_',\n '[': '',\n ']': '',\n '(': '',\n ')': '',\n '{': '',\n '}': '',\n '!': '-i',\n '&': 'and-',\n '<': 'lt-',\n '=': 'eq-',\n '>': 'gt-',\n '|': 'or-',\n '@': 'at-',\n '?': 'q-',\n '\\\\': '-',\n '\"': '-',\n \"'\": '-',\n '*': '-',\n '+': '-',\n ';': '-',\n '^': '-',\n '`': '-',\n '~': '-',\n '$': '-',\n}\n\nfunction escapeForRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * Replace escaped special characters in CSS selectors.\n *\n * Tailwind generates selectors like `.sm\\:text-base`. This function\n * replaces the `\\:` with `-` (or whatever the configured replacement is)\n * so the selector becomes `.sm-text-base`, which is safe for email clients.\n */\nfunction processCssSelectors(css: string, replacements: Record<string, string>): string {\n // Matches \\<char> in CSS selectors — e.g. \\: \\/ \\. \\[ etc.\n const selectorRegex = new RegExp(\n `\\\\\\\\(${Object.keys(replacements).map(escapeForRegex).join('|')})`,\n 'g',\n )\n\n return postcss([\n (root: postcss.Root) => {\n root.walkRules((rule: postcss.Rule) => {\n rule.selector = rule.selector\n .replace(selectorRegex, (matched, char, offset, str) => {\n /**\n * Yahoo Mail wraps content in a class literally named `&`, so\n * the selector `.\\&` must be preserved. Detect it as a\n * `\\&` that follows a `.` and ends the class atom\n * (space, combinator, comma, `{`, or end-of-str).\n */\n if (char === '&' && str[offset - 1] === '.') {\n const next = str[offset + 2]\n if (next === undefined || /[\\s,{>~+)]/.test(next)) return matched\n }\n return replacements[char] ?? matched\n })\n // Handle CSS unicode escape for comma (\\2c → _)\n .replaceAll('\\\\2c ', '_')\n })\n },\n ]).process(css, { parser: safeParser }).css\n}\n\n/**\n * Replace unsafe special characters in a class attribute value.\n *\n * Splits on whitespace and replaces each char from the replacements map\n * in each class token individually.\n */\nfunction processClassAttr(classStr: string, replacements: Record<string, string>): string {\n return classStr\n .split(/\\s+/)\n .filter(Boolean)\n .map((cls) => {\n for (const [from, to] of Object.entries(replacements)) {\n cls = cls.split(from).join(to)\n }\n return cls\n })\n .join(' ')\n}\n\n/**\n * Safe selectors transformer.\n *\n * Replaces unsafe characters (`:`, `/`, `[`, `]`, etc.) in:\n * - CSS selectors inside `<style>` tags\n * - HTML `class` attributes\n *\n * This makes Tailwind utility classes like `sm:text-base` safe for\n * email clients that cannot handle escaped characters in class names.\n *\n * Enabled by default. Disable by setting `css.safe` to `false`.\n * Customize replacements by passing a `Record<string, string>` — user\n * values are merged on top of the defaults.\n *\n * @param html HTML string to transform.\n * @param config CSS config (see {@link CssConfig}).\n * @returns The transformed HTML string.\n *\n * @example\n * import { safeSelectors } from '@maizzle/framework'\n *\n * const out = safeSelectors('<div class=\"sm:text-base\"></div>')\n */\nexport function safeSelectors(html: string, config: CssConfig = {}): string {\n return serialize(safeSelectorsDom(parse(html), config))\n}\n\n/**\n * DOM-form of {@link safeSelectors} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function safeSelectorsDom(dom: ChildNode[], config: CssConfig = {}): ChildNode[] {\n const option = config.safe ?? true\n\n if (!option) return dom\n\n const replacements: Record<string, string> =\n option && typeof option === 'object'\n ? { ...DEFAULT_REPLACEMENTS, ...option }\n : DEFAULT_REPLACEMENTS\n\n walk(dom, (node) => {\n const el = node as Element\n\n // Process CSS selectors inside <style> tags\n if (el.name === 'style' && el.children?.length) {\n const text = el.children.find((c) => c.type === 'text') as any\n if (text?.data?.trim()) {\n text.data = processCssSelectors(text.data, replacements)\n }\n }\n\n // Replace special chars in class attributes\n if ('attribs' in el && el.attribs?.class) {\n el.attribs.class = processClassAttr(el.attribs.class, replacements)\n }\n })\n\n return dom\n}\n"],"mappings":";;;;;;;AAMA,MAAM,uBAA+C;CACnD,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,MAAM;CACN,MAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;AACP;AAEA,SAAS,eAAe,GAAmB;CACzC,OAAO,EAAE,QAAQ,uBAAuB,MAAM;AAChD;;;;;;;;AASA,SAAS,oBAAoB,KAAa,cAA8C;CAEtF,MAAM,gBAAgB,IAAI,OACxB,QAAQ,OAAO,KAAK,YAAY,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,KAAK,GAAG,EAAE,IAChE,GACF;CAEA,OAAO,QAAQ,EACZ,SAAuB;EACtB,KAAK,WAAW,SAAuB;GACrC,KAAK,WAAW,KAAK,SAClB,QAAQ,gBAAgB,SAAS,MAAM,QAAQ,QAAQ;;;;;;;IAOtD,IAAI,SAAS,OAAO,IAAI,SAAS,OAAO,KAAK;KAC3C,MAAM,OAAO,IAAI,SAAS;KAC1B,IAAI,SAAS,KAAA,KAAa,aAAa,KAAK,IAAI,GAAG,OAAO;IAC5D;IACA,OAAO,aAAa,SAAS;GAC/B,CAAC,CAAC,CAED,WAAW,SAAS,GAAG;EAC5B,CAAC;CACH,CACF,CAAC,CAAC,CAAC,QAAQ,KAAK,EAAE,QAAQ,WAAW,CAAC,CAAC,CAAC;AAC1C;;;;;;;AAQA,SAAS,iBAAiB,UAAkB,cAA8C;CACxF,OAAO,SACJ,MAAM,KAAK,CAAC,CACZ,OAAO,OAAO,CAAC,CACf,KAAK,QAAQ;EACZ,KAAK,MAAM,CAAC,MAAM,OAAO,OAAO,QAAQ,YAAY,GAClD,MAAM,IAAI,MAAM,IAAI,CAAC,CAAC,KAAK,EAAE;EAE/B,OAAO;CACT,CAAC,CAAC,CACD,KAAK,GAAG;AACb;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,cAAc,MAAc,SAAoB,CAAC,GAAW;CAC1E,OAAO,UAAU,iBAAiB,MAAM,IAAI,GAAG,MAAM,CAAC;AACxD;;;;;;AAOA,SAAgB,iBAAiB,KAAkB,SAAoB,CAAC,GAAgB;CACtF,MAAM,SAAS,OAAO,QAAQ;CAE9B,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,eACJ,UAAU,OAAO,WAAW,WACxB;EAAE,GAAG;EAAsB,GAAG;CAAO,IACrC;CAEN,KAAK,MAAM,SAAS;EAClB,MAAM,KAAK;EAGX,IAAI,GAAG,SAAS,WAAW,GAAG,UAAU,QAAQ;GAC9C,MAAM,OAAO,GAAG,SAAS,MAAM,MAAM,EAAE,SAAS,MAAM;GACtD,IAAI,MAAM,MAAM,KAAK,GACnB,KAAK,OAAO,oBAAoB,KAAK,MAAM,YAAY;EAE3D;EAGA,IAAI,aAAa,MAAM,GAAG,SAAS,OACjC,GAAG,QAAQ,QAAQ,iBAAiB,GAAG,QAAQ,OAAO,YAAY;CAEtE,CAAC;CAED,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shorthandCss.js","names":[],"sources":["../../src/transformers/shorthandCss.ts"],"sourcesContent":["import postcss from 'postcss'\nimport safeParser from 'postcss-safe-parser'\nimport mergeLonghand from 'postcss-merge-longhand'\nimport type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize, walk } from '../utils/ast/index.ts'\n\n/**\n * Options for the `shorthandCss` transformer.\n */\nexport interface ShorthandCssOptions {\n /**\n * Restrict the transform to a list of HTML tag names. Omit to apply to\n * every element with a `style` attribute.\n *\n * @example ['td', 'div']\n */\n tags?: string[]\n}\n\n/**\n * Rewrite longhand CSS inside inline `style` attributes with shorthand\n * syntax. Works with margin, padding, and border when all sides are\n * specified.\n *\n * For example:\n * `margin-left: 2px; margin-right: 2px; margin-top: 4px; margin-bottom: 4px`\n * becomes:\n * `margin: 4px 2px`\n *\n * @param html HTML string to transform.\n * @param options Optional Maizzle options (`tags`).\n * @returns The transformed HTML string.\n *\n * @example\n * import { shorthandCss } from '@maizzle/framework'\n *\n * const out = shorthandCss(\n * '<p style=\"margin-top: 4px; margin-right: 2px; margin-bottom: 4px; margin-left: 2px;\">x</p>',\n * { tags: ['p'] },\n * )\n */\nexport function shorthandCss(html: string, options: ShorthandCssOptions = {}): string {\n return serialize(shorthandCssDom(parse(html), options))\n}\n\n/**\n * DOM-form of {@link shorthandCss} used by the internal transformer\n * pipeline. Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function shorthandCssDom(dom: ChildNode[], options: ShorthandCssOptions = {}): ChildNode[] {\n const allowedTags = options.tags ?? []\n const hasTagFilter = allowedTags.length > 0\n\n /**\n * Merge longhand within a single inline-style value. Returns the merged\n * string when shorter, otherwise the original. Wraps the value in a\n * dummy selector since postcss-merge-longhand operates on rules.\n */\n const mergeStyleValue = (styleValue: string): string => {\n try {\n const { css } = postcss()\n .use(mergeLonghand)\n .process(`div { ${styleValue} }`, { parser: safeParser })\n const match = css.match(/div\\s*\\{\\s*([^}]+)\\s*\\}/)\n if (match && match[1]) {\n const merged = match[1].trim()\n if (merged !== styleValue) return merged\n }\n }\n catch {}\n return styleValue\n }\n\n walk(dom, (node) => {\n /**\n * MSO conditional comments carry their own inline-style attributes\n * (e.g. `<!--[if mso]><td style=\"…\"><![endif]-->`) as opaque text.\n * The element walker can't see them, so without this branch the td/\n * v:rect styles inside comments stay longhand even when the visible\n * div has already been merged. Match each `style=\"…\"` substring,\n * run it through mergeLonghand, splice back.\n *\n * Tag filter intentionally bypassed: the user can't address MSO td\n * elements (they don't parse as elements), and these comments\n * always wrap email-layout primitives anyway.\n */\n if (node.type === 'comment') {\n const data = (node as any).data as string\n if (!data || !data.includes('style=\"')) return\n const newData = data.replace(/style=\"([^\"]*)\"/g, (full, value) => {\n const merged = mergeStyleValue(value)\n return merged === value ? full : `style=\"${merged}\"`\n })\n if (newData !== data) (node as any).data = newData\n return\n }\n\n const el = node as Element\n\n if (!el.attribs?.style) return\n if (hasTagFilter && !allowedTags.includes(el.name)) return\n\n const merged = mergeStyleValue(el.attribs.style)\n if (merged !== el.attribs.style) el.attribs.style = merged\n })\n\n return dom\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,aAAa,MAAc,UAA+B,CAAC,GAAW;CACpF,OAAO,UAAU,gBAAgB,MAAM,IAAI,GAAG,OAAO,CAAC;AACxD;;;;;;AAOA,SAAgB,gBAAgB,KAAkB,UAA+B,CAAC,GAAgB;CAChG,MAAM,cAAc,QAAQ,QAAQ,CAAC;CACrC,MAAM,eAAe,YAAY,SAAS;;;;;;CAO1C,MAAM,mBAAmB,eAA+B;EACtD,IAAI;GACF,MAAM,EAAE,QAAQ,QAAQ,
|
|
1
|
+
{"version":3,"file":"shorthandCss.js","names":[],"sources":["../../src/transformers/shorthandCss.ts"],"sourcesContent":["import postcss from 'postcss'\nimport safeParser from 'postcss-safe-parser'\nimport mergeLonghand from 'postcss-merge-longhand'\nimport type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize, walk } from '../utils/ast/index.ts'\n\n/**\n * Options for the `shorthandCss` transformer.\n */\nexport interface ShorthandCssOptions {\n /**\n * Restrict the transform to a list of HTML tag names. Omit to apply to\n * every element with a `style` attribute.\n *\n * @example ['td', 'div']\n */\n tags?: string[]\n}\n\n/**\n * Rewrite longhand CSS inside inline `style` attributes with shorthand\n * syntax. Works with margin, padding, and border when all sides are\n * specified.\n *\n * For example:\n * `margin-left: 2px; margin-right: 2px; margin-top: 4px; margin-bottom: 4px`\n * becomes:\n * `margin: 4px 2px`\n *\n * @param html HTML string to transform.\n * @param options Optional Maizzle options (`tags`).\n * @returns The transformed HTML string.\n *\n * @example\n * import { shorthandCss } from '@maizzle/framework'\n *\n * const out = shorthandCss(\n * '<p style=\"margin-top: 4px; margin-right: 2px; margin-bottom: 4px; margin-left: 2px;\">x</p>',\n * { tags: ['p'] },\n * )\n */\nexport function shorthandCss(html: string, options: ShorthandCssOptions = {}): string {\n return serialize(shorthandCssDom(parse(html), options))\n}\n\n/**\n * DOM-form of {@link shorthandCss} used by the internal transformer\n * pipeline. Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function shorthandCssDom(dom: ChildNode[], options: ShorthandCssOptions = {}): ChildNode[] {\n const allowedTags = options.tags ?? []\n const hasTagFilter = allowedTags.length > 0\n\n /**\n * Merge longhand within a single inline-style value. Returns the merged\n * string when shorter, otherwise the original. Wraps the value in a\n * dummy selector since postcss-merge-longhand operates on rules.\n */\n const mergeStyleValue = (styleValue: string): string => {\n try {\n const { css } = postcss()\n .use(mergeLonghand)\n .process(`div { ${styleValue} }`, { parser: safeParser })\n const match = css.match(/div\\s*\\{\\s*([^}]+)\\s*\\}/)\n if (match && match[1]) {\n const merged = match[1].trim()\n if (merged !== styleValue) return merged\n }\n }\n catch {}\n return styleValue\n }\n\n walk(dom, (node) => {\n /**\n * MSO conditional comments carry their own inline-style attributes\n * (e.g. `<!--[if mso]><td style=\"…\"><![endif]-->`) as opaque text.\n * The element walker can't see them, so without this branch the td/\n * v:rect styles inside comments stay longhand even when the visible\n * div has already been merged. Match each `style=\"…\"` substring,\n * run it through mergeLonghand, splice back.\n *\n * Tag filter intentionally bypassed: the user can't address MSO td\n * elements (they don't parse as elements), and these comments\n * always wrap email-layout primitives anyway.\n */\n if (node.type === 'comment') {\n const data = (node as any).data as string\n if (!data || !data.includes('style=\"')) return\n const newData = data.replace(/style=\"([^\"]*)\"/g, (full, value) => {\n const merged = mergeStyleValue(value)\n return merged === value ? full : `style=\"${merged}\"`\n })\n if (newData !== data) (node as any).data = newData\n return\n }\n\n const el = node as Element\n\n if (!el.attribs?.style) return\n if (hasTagFilter && !allowedTags.includes(el.name)) return\n\n const merged = mergeStyleValue(el.attribs.style)\n if (merged !== el.attribs.style) el.attribs.style = merged\n })\n\n return dom\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,aAAa,MAAc,UAA+B,CAAC,GAAW;CACpF,OAAO,UAAU,gBAAgB,MAAM,IAAI,GAAG,OAAO,CAAC;AACxD;;;;;;AAOA,SAAgB,gBAAgB,KAAkB,UAA+B,CAAC,GAAgB;CAChG,MAAM,cAAc,QAAQ,QAAQ,CAAC;CACrC,MAAM,eAAe,YAAY,SAAS;;;;;;CAO1C,MAAM,mBAAmB,eAA+B;EACtD,IAAI;GACF,MAAM,EAAE,QAAQ,QAAQ,CAAC,CACtB,IAAI,aAAa,CAAC,CAClB,QAAQ,SAAS,WAAW,KAAK,EAAE,QAAQ,WAAW,CAAC;GAC1D,MAAM,QAAQ,IAAI,MAAM,yBAAyB;GACjD,IAAI,SAAS,MAAM,IAAI;IACrB,MAAM,SAAS,MAAM,EAAE,CAAC,KAAK;IAC7B,IAAI,WAAW,YAAY,OAAO;GACpC;EACF,QACM,CAAC;EACP,OAAO;CACT;CAEA,KAAK,MAAM,SAAS;;;;;;;;;;;;;EAalB,IAAI,KAAK,SAAS,WAAW;GAC3B,MAAM,OAAQ,KAAa;GAC3B,IAAI,CAAC,QAAQ,CAAC,KAAK,SAAS,UAAS,GAAG;GACxC,MAAM,UAAU,KAAK,QAAQ,qBAAqB,MAAM,UAAU;IAChE,MAAM,SAAS,gBAAgB,KAAK;IACpC,OAAO,WAAW,QAAQ,OAAO,UAAU,OAAO;GACpD,CAAC;GACD,IAAI,YAAY,MAAM,KAAc,OAAO;GAC3C;EACF;EAEA,MAAM,KAAK;EAEX,IAAI,CAAC,GAAG,SAAS,OAAO;EACxB,IAAI,gBAAgB,CAAC,YAAY,SAAS,GAAG,IAAI,GAAG;EAEpD,MAAM,SAAS,gBAAgB,GAAG,QAAQ,KAAK;EAC/C,IAAI,WAAW,GAAG,QAAQ,OAAO,GAAG,QAAQ,QAAQ;CACtD,CAAC;CAED,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tailwindComponent.js","names":[],"sources":["../../src/transformers/tailwindComponent.ts"],"sourcesContent":["import { resolve } from 'node:path'\nimport type { ChildNode, Element, Comment } from 'domhandler'\nimport { walk } from '../utils/ast/index.ts'\nimport { compileTailwindCss } from '../utils/compileTailwindCss.ts'\nimport type { TailwindBlock } from '../composables/renderContext.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\nconst DEFAULT_SEED = '@import \"@maizzle/tailwindcss\" source(none);'\n\ninterface BlockMeta {\n id: string\n configCss?: string\n nested: boolean\n classes: Set<string>\n}\n\nconst OPEN_RE = /^mz-tw:(\\S+)$/\nconst CLOSE_RE = /^\\/mz-tw:(\\S+)$/\n\n/**\n * Compile Tailwind CSS for each top-level <Tailwind> block in the render\n * context. Nested <Tailwind> instances are flattened: their classes flow\n * up to the outermost block, their `#config` slot (if any) is ignored.\n * One <style> per outermost block is appended to <head>; marker comments\n * are stripped after.\n */\nexport async function tailwindComponent(\n dom: ChildNode[],\n blocks: TailwindBlock[],\n config: MaizzleConfig,\n filePath?: string,\n): Promise<ChildNode[]> {\n if (!blocks.length) return dom\n\n const map = new Map<string, BlockMeta>()\n for (const b of blocks) {\n map.set(b.id, { id: b.id, configCss: b.css, nested: false, classes: new Set() })\n }\n\n const stack: string[] = []\n const markers: Comment[] = []\n\n walk(dom, (node) => {\n if (node.type === 'comment') {\n const data = (node as Comment).data\n const open = data.match(OPEN_RE)\n const close = data.match(CLOSE_RE)\n if (open) {\n const id = open[1]\n const meta = map.get(id)\n if (meta && stack.length > 0) meta.nested = true\n if (meta) stack.push(id)\n markers.push(node as Comment)\n } else if (close) {\n const id = close[1]\n if (stack[stack.length - 1] === id) stack.pop()\n markers.push(node as Comment)\n }\n return\n }\n\n const el = node as Element\n /**\n * Always assign to the OUTERMOST active marker (stack[0]) so nested\n * <Tailwind> blocks merge their classes into the parent's scope.\n */\n if (el.attribs?.class && stack.length > 0) {\n map.get(stack[0])!.classes.add(el.attribs.class)\n }\n })\n\n const fromPath = filePath ?? resolve(process.cwd(), 'template.vue')\n\n let head: Element | undefined\n walk(dom, (n) => {\n if (!head && (n as Element).name === 'head') head = n as Element\n })\n\n if (!head) {\n throw new Error('`Tailwind` component requires `Head` component to be present in the template.')\n }\n\n /**\n * Compile + inject one <style raw> per outermost block. `raw` opts\n * the existing tailwindcss transformer out of recompiling\n * already-compiled CSS.\n */\n for (const meta of map.values()) {\n if (meta.nested) continue\n\n const cssInput = buildCssInput(meta.configCss, meta.classes)\n const css = (await compileTailwindCss(cssInput, config, `${fromPath}?tw=${meta.id}`)).trim()\n if (!css) continue\n\n const styleNode: Element = {\n type: 'tag',\n name: 'style',\n attribs: { raw: '' },\n children: [],\n parent: head,\n prev: null,\n next: null,\n } as any\n\n const textNode = {\n type: 'text',\n data: css,\n parent: styleNode,\n prev: null,\n next: null,\n } as any\n\n styleNode.children = [textNode]\n head.children.push(styleNode)\n }\n\n // Strip marker comments from their parents\n for (const c of markers) {\n const parent = c.parent as Element | null\n if (!parent?.children) continue\n const i = parent.children.indexOf(c)\n if (i >= 0) parent.children.splice(i, 1)\n }\n\n return dom\n}\n\nfunction buildCssInput(configCss: string | undefined, classes: Set<string>): string {\n const seed = configCss ?? DEFAULT_SEED\n\n if (!classes.size) return seed\n\n const inline = [...classes].join(' ').replace(/\"/g, '\\\\\"')\n return `${seed}\\n@source inline(\"${inline}\");`\n}\n"],"mappings":";;;;;AAOA,MAAM,eAAe;AASrB,MAAM,UAAU;AAChB,MAAM,WAAW;;;;;;;;AASjB,eAAsB,kBACpB,KACA,QACA,QACA,UACsB;CACtB,IAAI,CAAC,OAAO,QAAQ,OAAO;CAE3B,MAAM,sBAAM,IAAI,IAAuB;CACvC,KAAK,MAAM,KAAK,QACd,IAAI,IAAI,EAAE,IAAI;EAAE,IAAI,EAAE;EAAI,WAAW,EAAE;EAAK,QAAQ;EAAO,yBAAS,IAAI,IAAI;CAAE,CAAC;CAGjF,MAAM,QAAkB,CAAC;CACzB,MAAM,UAAqB,CAAC;CAE5B,KAAK,MAAM,SAAS;EAClB,IAAI,KAAK,SAAS,WAAW;GAC3B,MAAM,OAAQ,KAAiB;GAC/B,MAAM,OAAO,KAAK,MAAM,OAAO;GAC/B,MAAM,QAAQ,KAAK,MAAM,QAAQ;GACjC,IAAI,MAAM;IACR,MAAM,KAAK,KAAK;IAChB,MAAM,OAAO,IAAI,IAAI,EAAE;IACvB,IAAI,QAAQ,MAAM,SAAS,GAAG,KAAK,SAAS;IAC5C,IAAI,MAAM,MAAM,KAAK,EAAE;IACvB,QAAQ,KAAK,IAAe;GAC9B,OAAO,IAAI,OAAO;IAChB,MAAM,KAAK,MAAM;IACjB,IAAI,MAAM,MAAM,SAAS,OAAO,IAAI,MAAM,IAAI;IAC9C,QAAQ,KAAK,IAAe;GAC9B;GACA;EACF;EAEA,MAAM,KAAK;;;;;EAKX,IAAI,GAAG,SAAS,SAAS,MAAM,SAAS,GACtC,IAAI,IAAI,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"tailwindComponent.js","names":[],"sources":["../../src/transformers/tailwindComponent.ts"],"sourcesContent":["import { resolve } from 'node:path'\nimport type { ChildNode, Element, Comment } from 'domhandler'\nimport { walk } from '../utils/ast/index.ts'\nimport { compileTailwindCss } from '../utils/compileTailwindCss.ts'\nimport type { TailwindBlock } from '../composables/renderContext.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\nconst DEFAULT_SEED = '@import \"@maizzle/tailwindcss\" source(none);'\n\ninterface BlockMeta {\n id: string\n configCss?: string\n nested: boolean\n classes: Set<string>\n}\n\nconst OPEN_RE = /^mz-tw:(\\S+)$/\nconst CLOSE_RE = /^\\/mz-tw:(\\S+)$/\n\n/**\n * Compile Tailwind CSS for each top-level <Tailwind> block in the render\n * context. Nested <Tailwind> instances are flattened: their classes flow\n * up to the outermost block, their `#config` slot (if any) is ignored.\n * One <style> per outermost block is appended to <head>; marker comments\n * are stripped after.\n */\nexport async function tailwindComponent(\n dom: ChildNode[],\n blocks: TailwindBlock[],\n config: MaizzleConfig,\n filePath?: string,\n): Promise<ChildNode[]> {\n if (!blocks.length) return dom\n\n const map = new Map<string, BlockMeta>()\n for (const b of blocks) {\n map.set(b.id, { id: b.id, configCss: b.css, nested: false, classes: new Set() })\n }\n\n const stack: string[] = []\n const markers: Comment[] = []\n\n walk(dom, (node) => {\n if (node.type === 'comment') {\n const data = (node as Comment).data\n const open = data.match(OPEN_RE)\n const close = data.match(CLOSE_RE)\n if (open) {\n const id = open[1]\n const meta = map.get(id)\n if (meta && stack.length > 0) meta.nested = true\n if (meta) stack.push(id)\n markers.push(node as Comment)\n } else if (close) {\n const id = close[1]\n if (stack[stack.length - 1] === id) stack.pop()\n markers.push(node as Comment)\n }\n return\n }\n\n const el = node as Element\n /**\n * Always assign to the OUTERMOST active marker (stack[0]) so nested\n * <Tailwind> blocks merge their classes into the parent's scope.\n */\n if (el.attribs?.class && stack.length > 0) {\n map.get(stack[0])!.classes.add(el.attribs.class)\n }\n })\n\n const fromPath = filePath ?? resolve(process.cwd(), 'template.vue')\n\n let head: Element | undefined\n walk(dom, (n) => {\n if (!head && (n as Element).name === 'head') head = n as Element\n })\n\n if (!head) {\n throw new Error('`Tailwind` component requires `Head` component to be present in the template.')\n }\n\n /**\n * Compile + inject one <style raw> per outermost block. `raw` opts\n * the existing tailwindcss transformer out of recompiling\n * already-compiled CSS.\n */\n for (const meta of map.values()) {\n if (meta.nested) continue\n\n const cssInput = buildCssInput(meta.configCss, meta.classes)\n const css = (await compileTailwindCss(cssInput, config, `${fromPath}?tw=${meta.id}`)).trim()\n if (!css) continue\n\n const styleNode: Element = {\n type: 'tag',\n name: 'style',\n attribs: { raw: '' },\n children: [],\n parent: head,\n prev: null,\n next: null,\n } as any\n\n const textNode = {\n type: 'text',\n data: css,\n parent: styleNode,\n prev: null,\n next: null,\n } as any\n\n styleNode.children = [textNode]\n head.children.push(styleNode)\n }\n\n // Strip marker comments from their parents\n for (const c of markers) {\n const parent = c.parent as Element | null\n if (!parent?.children) continue\n const i = parent.children.indexOf(c)\n if (i >= 0) parent.children.splice(i, 1)\n }\n\n return dom\n}\n\nfunction buildCssInput(configCss: string | undefined, classes: Set<string>): string {\n const seed = configCss ?? DEFAULT_SEED\n\n if (!classes.size) return seed\n\n const inline = [...classes].join(' ').replace(/\"/g, '\\\\\"')\n return `${seed}\\n@source inline(\"${inline}\");`\n}\n"],"mappings":";;;;;AAOA,MAAM,eAAe;AASrB,MAAM,UAAU;AAChB,MAAM,WAAW;;;;;;;;AASjB,eAAsB,kBACpB,KACA,QACA,QACA,UACsB;CACtB,IAAI,CAAC,OAAO,QAAQ,OAAO;CAE3B,MAAM,sBAAM,IAAI,IAAuB;CACvC,KAAK,MAAM,KAAK,QACd,IAAI,IAAI,EAAE,IAAI;EAAE,IAAI,EAAE;EAAI,WAAW,EAAE;EAAK,QAAQ;EAAO,yBAAS,IAAI,IAAI;CAAE,CAAC;CAGjF,MAAM,QAAkB,CAAC;CACzB,MAAM,UAAqB,CAAC;CAE5B,KAAK,MAAM,SAAS;EAClB,IAAI,KAAK,SAAS,WAAW;GAC3B,MAAM,OAAQ,KAAiB;GAC/B,MAAM,OAAO,KAAK,MAAM,OAAO;GAC/B,MAAM,QAAQ,KAAK,MAAM,QAAQ;GACjC,IAAI,MAAM;IACR,MAAM,KAAK,KAAK;IAChB,MAAM,OAAO,IAAI,IAAI,EAAE;IACvB,IAAI,QAAQ,MAAM,SAAS,GAAG,KAAK,SAAS;IAC5C,IAAI,MAAM,MAAM,KAAK,EAAE;IACvB,QAAQ,KAAK,IAAe;GAC9B,OAAO,IAAI,OAAO;IAChB,MAAM,KAAK,MAAM;IACjB,IAAI,MAAM,MAAM,SAAS,OAAO,IAAI,MAAM,IAAI;IAC9C,QAAQ,KAAK,IAAe;GAC9B;GACA;EACF;EAEA,MAAM,KAAK;;;;;EAKX,IAAI,GAAG,SAAS,SAAS,MAAM,SAAS,GACtC,IAAI,IAAI,MAAM,EAAE,CAAC,CAAE,QAAQ,IAAI,GAAG,QAAQ,KAAK;CAEnD,CAAC;CAED,MAAM,WAAW,YAAY,QAAQ,QAAQ,IAAI,GAAG,cAAc;CAElE,IAAI;CACJ,KAAK,MAAM,MAAM;EACf,IAAI,CAAC,QAAS,EAAc,SAAS,QAAQ,OAAO;CACtD,CAAC;CAED,IAAI,CAAC,MACH,MAAM,IAAI,MAAM,+EAA+E;;;;;;CAQjG,KAAK,MAAM,QAAQ,IAAI,OAAO,GAAG;EAC/B,IAAI,KAAK,QAAQ;EAGjB,MAAM,OAAO,MAAM,mBADF,cAAc,KAAK,WAAW,KAAK,OACP,GAAG,QAAQ,GAAG,SAAS,MAAM,KAAK,IAAI,EAAA,CAAG,KAAK;EAC3F,IAAI,CAAC,KAAK;EAEV,MAAM,YAAqB;GACzB,MAAM;GACN,MAAM;GACN,SAAS,EAAE,KAAK,GAAG;GACnB,UAAU,CAAC;GACX,QAAQ;GACR,MAAM;GACN,MAAM;EACR;EAUA,UAAU,WAAW,CAAC;GAPpB,MAAM;GACN,MAAM;GACN,QAAQ;GACR,MAAM;GACN,MAAM;EAGqB,CAAC;EAC9B,KAAK,SAAS,KAAK,SAAS;CAC9B;CAGA,KAAK,MAAM,KAAK,SAAS;EACvB,MAAM,SAAS,EAAE;EACjB,IAAI,CAAC,QAAQ,UAAU;EACvB,MAAM,IAAI,OAAO,SAAS,QAAQ,CAAC;EACnC,IAAI,KAAK,GAAG,OAAO,SAAS,OAAO,GAAG,CAAC;CACzC;CAEA,OAAO;AACT;AAEA,SAAS,cAAc,WAA+B,SAA8B;CAClF,MAAM,OAAO,aAAa;CAE1B,IAAI,CAAC,QAAQ,MAAM,OAAO;CAG1B,OAAO,GAAG,KAAK,oBADA,CAAC,GAAG,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,QAAQ,MAAM,MACZ,EAAE;AAC5C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tailwindcss.js","names":[],"sources":["../../src/transformers/tailwindcss.ts"],"sourcesContent":["import { resolve, dirname, relative } from 'node:path'\nimport type { ChildNode, Element } from 'domhandler'\nimport { walk } from '../utils/ast/index.ts'\nimport { decodeStyleEntities } from '../utils/decodeStyleEntities.ts'\nimport { compileTailwindCss } from '../utils/compileTailwindCss.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\n/**\n * Check if CSS content uses Tailwind features that require source scanning.\n *\n * Only CSS that imports Tailwind (or @maizzle/tailwindcss) needs @source\n * directives. Plain CSS without Tailwind imports doesn't need scanning\n * and would pass through @source directives unconsumed.\n */\nfunction usesTailwind(css: string): boolean {\n return /((@import|@reference)\\s+[\"'](tailwindcss|@maizzle\\/tailwindcss)|@tailwind\\s)/.test(css)\n}\n\n/**\n * Build @source directives for Tailwind CSS scanning.\n *\n * Configures two types of sources:\n * 1. Exclusions for output dir and user-configured paths\n * 2. Inline source with all class attribute values from the rendered DOM,\n * capturing classes from all components (built-in + user), dynamic\n * expressions, and the template itself — Tailwind's scanner handles\n * the actual class extraction from these raw values\n */\nfunction buildSourceDirectives(dom: ChildNode[], config: MaizzleConfig, fromDir: string): string {\n const directives: string[] = []\n\n // Exclude output dir and user-configured paths\n const excludePaths = [\n resolve(config.output?.path ?? 'dist'),\n ...(config.css?.exclude ?? []).map(p => resolve(p)),\n ]\n\n for (const p of excludePaths) {\n directives.push(`@source not \"${relative(fromDir, resolve(p))}\";`)\n }\n\n /**\n * Inline source: collect all class attribute values from the rendered DOM.\n * After Vue SSR, the DOM contains every class from every component\n * (built-in framework components, user components, dynamic\n * bindings). We pass these raw values to Tailwind's\n * scanner via @source inline().\n */\n const classes: string[] = []\n walk(dom, (n) => {\n const cls = (n as Element).attribs?.class\n if (cls) classes.push(cls)\n })\n\n if (classes.length) {\n directives.push(`@source inline(\"${classes.join(' ')}\");`)\n }\n\n return directives.join('\\n')\n}\n\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
|
|
1
|
+
{"version":3,"file":"tailwindcss.js","names":[],"sources":["../../src/transformers/tailwindcss.ts"],"sourcesContent":["import { resolve, dirname, relative } from 'node:path'\nimport type { ChildNode, Element } from 'domhandler'\nimport { walk } from '../utils/ast/index.ts'\nimport { decodeStyleEntities } from '../utils/decodeStyleEntities.ts'\nimport { compileTailwindCss } from '../utils/compileTailwindCss.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\n/**\n * Check if CSS content uses Tailwind features that require source scanning.\n *\n * Only CSS that imports Tailwind (or @maizzle/tailwindcss) needs @source\n * directives. Plain CSS without Tailwind imports doesn't need scanning\n * and would pass through @source directives unconsumed.\n */\nfunction usesTailwind(css: string): boolean {\n return /((@import|@reference)\\s+[\"'](tailwindcss|@maizzle\\/tailwindcss)|@tailwind\\s)/.test(css)\n}\n\n/**\n * Build @source directives for Tailwind CSS scanning.\n *\n * Configures two types of sources:\n * 1. Exclusions for output dir and user-configured paths\n * 2. Inline source with all class attribute values from the rendered DOM,\n * capturing classes from all components (built-in + user), dynamic\n * expressions, and the template itself — Tailwind's scanner handles\n * the actual class extraction from these raw values\n */\nfunction buildSourceDirectives(dom: ChildNode[], config: MaizzleConfig, fromDir: string): string {\n const directives: string[] = []\n\n // Exclude output dir and user-configured paths\n const excludePaths = [\n resolve(config.output?.path ?? 'dist'),\n ...(config.css?.exclude ?? []).map(p => resolve(p)),\n ]\n\n for (const p of excludePaths) {\n directives.push(`@source not \"${relative(fromDir, resolve(p))}\";`)\n }\n\n /**\n * Inline source: collect all class attribute values from the rendered DOM.\n * After Vue SSR, the DOM contains every class from every component\n * (built-in framework components, user components, dynamic\n * bindings). We pass these raw values to Tailwind's\n * scanner via @source inline().\n */\n const classes: string[] = []\n walk(dom, (n) => {\n const cls = (n as Element).attribs?.class\n if (cls) classes.push(cls)\n })\n\n if (classes.length) {\n directives.push(`@source inline(\"${classes.join(' ')}\");`)\n }\n\n return directives.join('\\n')\n}\n\n/**\n * Tailwind CSS transformer.\n *\n * Compiles CSS inside <style> tags in the DOM using\n * @tailwindcss/postcss, then lowers modern CSS syntax with lightningcss.\n *\n * Configures Tailwind sources to scan:\n * - Rendered class attributes (via `@source inline`) for all classes from all components\n * - User project files (via Tailwind's auto-detection from base/from path)\n *\n * User `@source` and `@source not directives` in style tags are preserved.\n * Source directives are only added to style tags that import Tailwind.\n *\n * Runs as the first transformer in the pipeline so that subsequent\n * transformers (inliner, purge, etc.) work with fully compiled CSS.\n */\nexport async function tailwindcss(dom: ChildNode[], config: MaizzleConfig, filePath?: string): Promise<ChildNode[]> {\n const styleTags: { node: Element; cssContent: string }[] = []\n\n walk(dom, (node) => {\n if ((node as Element).name !== 'style') return\n\n const el = node as Element\n const attrs = el.attribs\n\n /**\n * `raw` opts out of compilation entirely (marker is consumed here).\n * `embed`/`data-embed` only signal \"preserve tag after inlining\"\n * — they still need to go through compile so Tailwind/@apply\n * resolves.\n */\n if ('raw' in attrs) {\n delete el.attribs.raw\n return\n }\n\n // Get text content from children and decode HTML entities\n const rawContent = el.children\n .filter(child => child.type === 'text')\n .map(child => (child as any).data)\n .join('')\n\n if (!rawContent.trim()) return\n\n styleTags.push({ node: el, cssContent: decodeStyleEntities(rawContent) })\n })\n\n if (!styleTags.length) return dom\n\n const fromPath = filePath ?? resolve(process.cwd(), 'template.vue')\n const fromDir = dirname(fromPath)\n\n // Only compute source directives if at least one style tag uses Tailwind\n const hasTailwindStyles = styleTags.some(({ cssContent }) => usesTailwind(cssContent))\n const sourceDirectives = hasTailwindStyles\n ? buildSourceDirectives(dom, config, fromDir)\n : ''\n\n for (let i = 0; i < styleTags.length; i++) {\n const { node, cssContent } = styleTags[i]\n\n /**\n * Only add source directives to style tags that import Tailwind —\n * plain CSS doesn't need them and @tailwindcss/postcss would\n * leave the directives unconsumed in the output.\n */\n const fullCss = usesTailwind(cssContent)\n ? `${cssContent}\\n${sourceDirectives}`\n : cssContent\n\n try {\n const optimized = await compileTailwindCss(fullCss, config, `${fromPath}?style=${i}`)\n\n // Replace the style tag's children with the compiled CSS\n node.children = [{\n type: 'text',\n data: optimized,\n parent: node,\n } as any]\n } catch {\n /**\n * If CSS processing fails, still replace with decoded content\n * so HTML entities don't break the CSS.\n */\n node.children = [{\n type: 'text',\n data: cssContent,\n parent: node,\n } as any]\n }\n }\n\n return dom\n}\n"],"mappings":";;;;;;;;;;;;;AAcA,SAAS,aAAa,KAAsB;CAC1C,OAAO,+EAA+E,KAAK,GAAG;AAChG;;;;;;;;;;;AAYA,SAAS,sBAAsB,KAAkB,QAAuB,SAAyB;CAC/F,MAAM,aAAuB,CAAC;CAG9B,MAAM,eAAe,CACnB,QAAQ,OAAO,QAAQ,QAAQ,MAAM,GACrC,IAAI,OAAO,KAAK,WAAW,CAAC,EAAA,CAAG,KAAI,MAAK,QAAQ,CAAC,CAAC,CACpD;CAEA,KAAK,MAAM,KAAK,cACd,WAAW,KAAK,gBAAgB,SAAS,SAAS,QAAQ,CAAC,CAAC,EAAE,GAAG;;;;;;;;CAUnE,MAAM,UAAoB,CAAC;CAC3B,KAAK,MAAM,MAAM;EACf,MAAM,MAAO,EAAc,SAAS;EACpC,IAAI,KAAK,QAAQ,KAAK,GAAG;CAC3B,CAAC;CAED,IAAI,QAAQ,QACV,WAAW,KAAK,mBAAmB,QAAQ,KAAK,GAAG,EAAE,IAAI;CAG3D,OAAO,WAAW,KAAK,IAAI;AAC7B;;;;;;;;;;;;;;;;;AAkBA,eAAsB,YAAY,KAAkB,QAAuB,UAAyC;CAClH,MAAM,YAAqD,CAAC;CAE5D,KAAK,MAAM,SAAS;EAClB,IAAK,KAAiB,SAAS,SAAS;EAExC,MAAM,KAAK;;;;;;;EASX,IAAI,SARU,GAAG,SAQG;GAClB,OAAO,GAAG,QAAQ;GAClB;EACF;EAGA,MAAM,aAAa,GAAG,SACnB,QAAO,UAAS,MAAM,SAAS,MAAM,CAAC,CACtC,KAAI,UAAU,MAAc,IAAI,CAAC,CACjC,KAAK,EAAE;EAEV,IAAI,CAAC,WAAW,KAAK,GAAG;EAExB,UAAU,KAAK;GAAE,MAAM;GAAI,YAAY,oBAAoB,UAAU;EAAE,CAAC;CAC1E,CAAC;CAED,IAAI,CAAC,UAAU,QAAQ,OAAO;CAE9B,MAAM,WAAW,YAAY,QAAQ,QAAQ,IAAI,GAAG,cAAc;CAClE,MAAM,UAAU,QAAQ,QAAQ;CAIhC,MAAM,mBADoB,UAAU,MAAM,EAAE,iBAAiB,aAAa,UAAU,CAC3C,IACrC,sBAAsB,KAAK,QAAQ,OAAO,IAC1C;CAEJ,KAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;EACzC,MAAM,EAAE,MAAM,eAAe,UAAU;;;;;;EAOvC,MAAM,UAAU,aAAa,UAAU,IACnC,GAAG,WAAW,IAAI,qBAClB;EAEJ,IAAI;GAIF,KAAK,WAAW,CAAC;IACf,MAAM;IACN,MAAM,MALgB,mBAAmB,SAAS,QAAQ,GAAG,SAAS,SAAS,GAAG;IAMlF,QAAQ;GACV,CAAQ;EACV,QAAQ;;;;;GAKN,KAAK,WAAW,CAAC;IACf,MAAM;IACN,MAAM;IACN,QAAQ;GACV,CAAQ;EACV;CACF;CAEA,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"urlQuery.js","names":[],"sources":["../../src/transformers/urlQuery.ts"],"sourcesContent":["import queryString from 'query-string'\nimport { selectAll } from 'css-select'\nimport type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize } from '../utils/ast/index.ts'\nimport { isAbsoluteUrl } from '../utils/url.ts'\nimport type { UrlQueryOptions } from '../types/config.ts'\n\nconst DEFAULT_ATTRIBUTES = ['src', 'href', 'poster', 'srcset', 'background']\nconst DEFAULT_TAGS = ['a']\n\n/**\n * Append query parameters to a URL string using query-string.\n */\nfunction appendParams(\n url: string,\n params: Record<string, unknown>,\n qsOptions: queryString.StringifyOptions,\n strict: boolean,\n): string {\n if (strict && !isAbsoluteUrl(url)) return url\n\n return queryString.stringifyUrl(\n { url, query: params as queryString.StringifiableRecord },\n qsOptions,\n )\n}\n\n/**\n * Append query parameters to URLs found in matching attributes/elements.\n *\n * @param html HTML string to transform.\n * @param params Query parameters to append (e.g. `{ utm_source: 'newsletter' }`).\n * @param options Behaviour overrides — `tags` (CSS selectors, default `['a']`),\n * `attributes` (default `['src', 'href', 'poster', 'srcset', 'background']`),\n * `strict` (default `true`, only rewrites absolute URLs),\n * `qs` (forwarded to `query-string`, default `{ encode: false }`).\n * @returns The transformed HTML string.\n *\n * @example\n * import { urlQuery } from '@maizzle/framework'\n *\n * const out = urlQuery(\n * '<a href=\"https://example.com\">x</a>',\n * { utm_source: 'newsletter' },\n * )\n *\n * // Restrict to specific tags / attributes:\n * urlQuery(html, { ref: 'email' }, { tags: ['a', 'img'], attributes: ['href', 'src'] })\n */\nexport function urlQuery(\n html: string,\n params: Record<string, unknown> = {},\n options: UrlQueryOptions = {},\n): string {\n return serialize(urlQueryDom(parse(html), params, options))\n}\n\n/**\n * DOM-form of {@link urlQuery} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function urlQueryDom(\n dom: ChildNode[],\n params: Record<string, unknown> = {},\n options: UrlQueryOptions = {},\n): ChildNode[] {\n if (!params || Object.keys(params).length === 0) return dom\n\n const tags = options.tags ?? DEFAULT_TAGS\n const attributes = options.attributes ?? DEFAULT_ATTRIBUTES\n const strict = options.strict ?? true\n const qsOptions: queryString.StringifyOptions = { encode: false, ...((options.qs ?? {}) as queryString.StringifyOptions) }\n\n // Use css-select to find all elements matching any of the tag selectors\n const selector = tags.join(', ')\n const elements = selectAll(selector, dom) as Element[]\n\n for (const el of elements) {\n for (const attr of attributes) {\n const value = el.attribs[attr]\n if (!value) continue\n\n const updated = appendParams(value, params, qsOptions, strict)\n if (updated !== value) {\n el.attribs[attr] = updated\n }\n }\n }\n\n return dom\n}\n"],"mappings":";;;;;;;AAOA,MAAM,qBAAqB;CAAC;CAAO;CAAQ;CAAU;CAAU;AAAY;AAC3E,MAAM,eAAe,CAAC,GAAG;;;;AAKzB,SAAS,aACP,KACA,QACA,WACA,QACQ;CACR,IAAI,UAAU,CAAC,cAAc,GAAG,GAAG,OAAO;CAE1C,OAAO,YAAY,aACjB;EAAE;EAAK,OAAO;CAA0C,GACxD,SACF;AACF;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,SACd,MACA,SAAkC,CAAC,GACnC,UAA2B,CAAC,GACpB;CACR,OAAO,UAAU,YAAY,MAAM,IAAI,GAAG,QAAQ,OAAO,CAAC;AAC5D;;;;;;AAOA,SAAgB,YACd,KACA,SAAkC,CAAC,GACnC,UAA2B,CAAC,GACf;CACb,IAAI,CAAC,UAAU,OAAO,KAAK,MAAM,
|
|
1
|
+
{"version":3,"file":"urlQuery.js","names":[],"sources":["../../src/transformers/urlQuery.ts"],"sourcesContent":["import queryString from 'query-string'\nimport { selectAll } from 'css-select'\nimport type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize } from '../utils/ast/index.ts'\nimport { isAbsoluteUrl } from '../utils/url.ts'\nimport type { UrlQueryOptions } from '../types/config.ts'\n\nconst DEFAULT_ATTRIBUTES = ['src', 'href', 'poster', 'srcset', 'background']\nconst DEFAULT_TAGS = ['a']\n\n/**\n * Append query parameters to a URL string using query-string.\n */\nfunction appendParams(\n url: string,\n params: Record<string, unknown>,\n qsOptions: queryString.StringifyOptions,\n strict: boolean,\n): string {\n if (strict && !isAbsoluteUrl(url)) return url\n\n return queryString.stringifyUrl(\n { url, query: params as queryString.StringifiableRecord },\n qsOptions,\n )\n}\n\n/**\n * Append query parameters to URLs found in matching attributes/elements.\n *\n * @param html HTML string to transform.\n * @param params Query parameters to append (e.g. `{ utm_source: 'newsletter' }`).\n * @param options Behaviour overrides — `tags` (CSS selectors, default `['a']`),\n * `attributes` (default `['src', 'href', 'poster', 'srcset', 'background']`),\n * `strict` (default `true`, only rewrites absolute URLs),\n * `qs` (forwarded to `query-string`, default `{ encode: false }`).\n * @returns The transformed HTML string.\n *\n * @example\n * import { urlQuery } from '@maizzle/framework'\n *\n * const out = urlQuery(\n * '<a href=\"https://example.com\">x</a>',\n * { utm_source: 'newsletter' },\n * )\n *\n * // Restrict to specific tags / attributes:\n * urlQuery(html, { ref: 'email' }, { tags: ['a', 'img'], attributes: ['href', 'src'] })\n */\nexport function urlQuery(\n html: string,\n params: Record<string, unknown> = {},\n options: UrlQueryOptions = {},\n): string {\n return serialize(urlQueryDom(parse(html), params, options))\n}\n\n/**\n * DOM-form of {@link urlQuery} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function urlQueryDom(\n dom: ChildNode[],\n params: Record<string, unknown> = {},\n options: UrlQueryOptions = {},\n): ChildNode[] {\n if (!params || Object.keys(params).length === 0) return dom\n\n const tags = options.tags ?? DEFAULT_TAGS\n const attributes = options.attributes ?? DEFAULT_ATTRIBUTES\n const strict = options.strict ?? true\n const qsOptions: queryString.StringifyOptions = { encode: false, ...((options.qs ?? {}) as queryString.StringifyOptions) }\n\n // Use css-select to find all elements matching any of the tag selectors\n const selector = tags.join(', ')\n const elements = selectAll(selector, dom) as Element[]\n\n for (const el of elements) {\n for (const attr of attributes) {\n const value = el.attribs[attr]\n if (!value) continue\n\n const updated = appendParams(value, params, qsOptions, strict)\n if (updated !== value) {\n el.attribs[attr] = updated\n }\n }\n }\n\n return dom\n}\n"],"mappings":";;;;;;;AAOA,MAAM,qBAAqB;CAAC;CAAO;CAAQ;CAAU;CAAU;AAAY;AAC3E,MAAM,eAAe,CAAC,GAAG;;;;AAKzB,SAAS,aACP,KACA,QACA,WACA,QACQ;CACR,IAAI,UAAU,CAAC,cAAc,GAAG,GAAG,OAAO;CAE1C,OAAO,YAAY,aACjB;EAAE;EAAK,OAAO;CAA0C,GACxD,SACF;AACF;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,SACd,MACA,SAAkC,CAAC,GACnC,UAA2B,CAAC,GACpB;CACR,OAAO,UAAU,YAAY,MAAM,IAAI,GAAG,QAAQ,OAAO,CAAC;AAC5D;;;;;;AAOA,SAAgB,YACd,KACA,SAAkC,CAAC,GACnC,UAA2B,CAAC,GACf;CACb,IAAI,CAAC,UAAU,OAAO,KAAK,MAAM,CAAC,CAAC,WAAW,GAAG,OAAO;CAExD,MAAM,OAAO,QAAQ,QAAQ;CAC7B,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,YAA0C;EAAE,QAAQ;EAAO,GAAK,QAAQ,MAAM,CAAC;CAAoC;CAIzH,MAAM,WAAW,UADA,KAAK,KAAK,IACO,GAAG,GAAG;CAExC,KAAK,MAAM,MAAM,UACf,KAAK,MAAM,QAAQ,YAAY;EAC7B,MAAM,QAAQ,GAAG,QAAQ;EACzB,IAAI,CAAC,OAAO;EAEZ,MAAM,UAAU,aAAa,OAAO,QAAQ,WAAW,MAAM;EAC7D,IAAI,YAAY,OACd,GAAG,QAAQ,QAAQ;CAEvB;CAGF,OAAO;AACT"}
|
package/dist/types/config.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { RemoveValue } from "../plugins/postcss/removeDeclarations.js";
|
|
2
2
|
import { TemplateInfo } from "../events/index.js";
|
|
3
3
|
import { Directive, Plugin } from "vue";
|
|
4
|
-
import
|
|
4
|
+
import juice from "juice";
|
|
5
5
|
import { InlineConfig } from "vite";
|
|
6
|
-
import { Options
|
|
6
|
+
import { Options } from "unplugin-vue-markdown/types";
|
|
7
7
|
|
|
8
8
|
//#region src/types/config.d.ts
|
|
9
|
+
type JuiceOptions = NonNullable<Parameters<typeof juice>[1]>;
|
|
9
10
|
interface UrlQueryOptions {
|
|
10
11
|
/**
|
|
11
12
|
* CSS selectors for elements to process.
|
|
@@ -94,7 +95,7 @@ interface CssConfig {
|
|
|
94
95
|
* }
|
|
95
96
|
* }
|
|
96
97
|
*/
|
|
97
|
-
inline?: boolean |
|
|
98
|
+
inline?: boolean | JuiceOptions & {
|
|
98
99
|
/**
|
|
99
100
|
* Convert HTML attributes like `width`, `height`, `bgcolor`, and `valign`
|
|
100
101
|
* to inline CSS styles. Set to `true` for all, or pass an array of attribute names.
|
|
@@ -362,7 +363,7 @@ interface HtmlConfig {
|
|
|
362
363
|
}
|
|
363
364
|
type FilterFunction = (str: string, value: string) => string;
|
|
364
365
|
type FiltersConfig = false | Record<string, FilterFunction>;
|
|
365
|
-
interface MarkdownConfig extends Options
|
|
366
|
+
interface MarkdownConfig extends Options {
|
|
366
367
|
/**
|
|
367
368
|
* The shiki theme to use for syntax highlighting in Markdown fenced code blocks.
|
|
368
369
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","names":[],"sources":["../../src/types/config.ts"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"config.d.ts","names":[],"sources":["../../src/types/config.ts"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serializer.js","names":[],"sources":["../../../src/utils/ast/serializer.ts"],"sourcesContent":["import render from 'dom-serializer'\nimport type { ChildNode, Element, Text } from 'domhandler'\nimport type { DomSerializerOptions } from 'dom-serializer'\n\nconst RAW_TEXT_ELEMENTS = new Set(['script', 'style'])\n\n/**\n * Re-encode `<` and `>` in text nodes before serialization.\n *\n * The parser decodes entities like `<` into raw `<` in text nodes. With\n * `encodeEntities: false` (needed so other transformers can emit entity\n * strings such as ` ` verbatim), the serializer would write those raw\n * `<` characters as-is, turning escaped text (e.g. a Vue `{{ html }}`\n * interpolation containing `<p>...</p>`) into real DOM downstream.\n *\n * `&` is intentionally not re-encoded: the `entities` transformer writes\n * literal entity strings (` `, `—`) directly into text-node data\n * and relies on the serializer leaving them alone.\n *\n * Skip `<script>` / `<style>` — their children are raw-text containers, and\n * CSS/JS legitimately contains `<` and `>`.\n */\nfunction encodeTextNodes(dom: ChildNode[], inRawText = false): void {\n for (const node of dom) {\n if (node.type === 'text') {\n if (!inRawText) {\n const text = node as Text\n text.data = text.data\n .replace(/</g, '<')\n .replace(/>/g, '>')\n }\n continue\n }\n\n if ('children' in node && (node as Element).children?.length) {\n const el = node as Element\n const nextRaw = inRawText || RAW_TEXT_ELEMENTS.has(el.name)\n encodeTextNodes(el.children as ChildNode[], nextRaw)\n }\n }\n}\n\nexport function serialize(dom: ChildNode[], options?: DomSerializerOptions): string {\n encodeTextNodes(dom)\n return render(dom, { encodeEntities: false, ...options })\n}\n"],"mappings":";;AAIA,MAAM,oBAAoB,IAAI,IAAI,CAAC,UAAU,OAAO,CAAC;;;;;;;;;;;;;;;;;AAkBrD,SAAS,gBAAgB,KAAkB,YAAY,OAAa;CAClE,KAAK,MAAM,QAAQ,KAAK;EACtB,IAAI,KAAK,SAAS,QAAQ;GACxB,IAAI,CAAC,WAAW;IACd,MAAM,OAAO;IACb,KAAK,OAAO,KAAK,KACd,QAAQ,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"serializer.js","names":[],"sources":["../../../src/utils/ast/serializer.ts"],"sourcesContent":["import render from 'dom-serializer'\nimport type { ChildNode, Element, Text } from 'domhandler'\nimport type { DomSerializerOptions } from 'dom-serializer'\n\nconst RAW_TEXT_ELEMENTS = new Set(['script', 'style'])\n\n/**\n * Re-encode `<` and `>` in text nodes before serialization.\n *\n * The parser decodes entities like `<` into raw `<` in text nodes. With\n * `encodeEntities: false` (needed so other transformers can emit entity\n * strings such as ` ` verbatim), the serializer would write those raw\n * `<` characters as-is, turning escaped text (e.g. a Vue `{{ html }}`\n * interpolation containing `<p>...</p>`) into real DOM downstream.\n *\n * `&` is intentionally not re-encoded: the `entities` transformer writes\n * literal entity strings (` `, `—`) directly into text-node data\n * and relies on the serializer leaving them alone.\n *\n * Skip `<script>` / `<style>` — their children are raw-text containers, and\n * CSS/JS legitimately contains `<` and `>`.\n */\nfunction encodeTextNodes(dom: ChildNode[], inRawText = false): void {\n for (const node of dom) {\n if (node.type === 'text') {\n if (!inRawText) {\n const text = node as Text\n text.data = text.data\n .replace(/</g, '<')\n .replace(/>/g, '>')\n }\n continue\n }\n\n if ('children' in node && (node as Element).children?.length) {\n const el = node as Element\n const nextRaw = inRawText || RAW_TEXT_ELEMENTS.has(el.name)\n encodeTextNodes(el.children as ChildNode[], nextRaw)\n }\n }\n}\n\nexport function serialize(dom: ChildNode[], options?: DomSerializerOptions): string {\n encodeTextNodes(dom)\n return render(dom, { encodeEntities: false, ...options })\n}\n"],"mappings":";;AAIA,MAAM,oBAAoB,IAAI,IAAI,CAAC,UAAU,OAAO,CAAC;;;;;;;;;;;;;;;;;AAkBrD,SAAS,gBAAgB,KAAkB,YAAY,OAAa;CAClE,KAAK,MAAM,QAAQ,KAAK;EACtB,IAAI,KAAK,SAAS,QAAQ;GACxB,IAAI,CAAC,WAAW;IACd,MAAM,OAAO;IACb,KAAK,OAAO,KAAK,KACd,QAAQ,MAAM,MAAM,CAAC,CACrB,QAAQ,MAAM,MAAM;GACzB;GACA;EACF;EAEA,IAAI,cAAc,QAAS,KAAiB,UAAU,QAAQ;GAC5D,MAAM,KAAK;GACX,MAAM,UAAU,aAAa,kBAAkB,IAAI,GAAG,IAAI;GAC1D,gBAAgB,GAAG,UAAyB,OAAO;EACrD;CACF;AACF;AAEA,SAAgB,UAAU,KAAkB,SAAwC;CAClF,gBAAgB,GAAG;CACnB,OAAO,OAAO,KAAK;EAAE,gBAAgB;EAAO,GAAG;CAAQ,CAAC;AAC1D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compileTailwindCss.js","names":["resolveProps","pruneVars"],"sources":["../../src/utils/compileTailwindCss.ts"],"sourcesContent":["import postcss from 'postcss'\nimport tailwindcssPostcss from '@tailwindcss/postcss'\nimport postcssCalc from 'postcss-calc'\nimport safeParser from 'postcss-safe-parser'\nimport { transform } from 'lightningcss'\nimport resolveProps from '../plugins/postcss/resolveProps.ts'\nimport pruneVars from '../plugins/postcss/pruneVars.ts'\nimport { tailwindCleanup } from '../plugins/postcss/tailwindCleanup.ts'\nimport { mergeMediaQueries } from '../plugins/postcss/mergeMediaQueries.ts'\nimport { quoteFontFamilies } from '../plugins/postcss/quoteFontFamilies.ts'\nimport { resolveMaizzleImports } from '../plugins/postcss/resolveMaizzleImports.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\nexport function createTailwindProcessor(config: MaizzleConfig) {\n return postcss([\n // Must run before @tailwindcss/postcss so it sees absolute import paths\n resolveMaizzleImports(),\n tailwindcssPostcss({\n base: config.css?.base,\n transformAssetUrls: false,\n optimize: false,\n }),\n resolveProps(),\n postcssCalc({}),\n pruneVars(),\n ])\n}\n\nexport function lowerCssSyntax(css: string): string {\n const result = transform({\n filename: 'email.css',\n code: Buffer.from(css),\n minify: false,\n targets: { ie: 4 << 5 },\n })\n\n return result.code.toString()\n}\n\nexport async function optimizeTailwindCss(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 * Compile a Tailwind CSS source string into final email-safe CSS:\n * runs @tailwindcss/postcss, lowers modern syntax via lightningcss,\n * then applies cleanup + media-query merging.\n */\nexport async function compileTailwindCss(\n cssInput: string,\n config: MaizzleConfig,\n from: string,\n): Promise<string> {\n const processor = createTailwindProcessor(config)\n const result = await processor.process(cssInput, { from, parser: safeParser })\n const lowered = lowerCssSyntax(result.css)\n return optimizeTailwindCss(lowered, config)\n}\n"],"mappings":";;;;;;;;;;;;AAaA,SAAgB,wBAAwB,QAAuB;CAC7D,OAAO,QAAQ;EAEb,sBAAsB;EACtB,mBAAmB;GACjB,MAAM,OAAO,KAAK;GAClB,oBAAoB;GACpB,UAAU;EACZ,CAAC;EACDA,qBAAa;EACb,YAAY,CAAC,CAAC;EACdC,kBAAU;CACZ,CAAC;AACH;AAEA,SAAgB,eAAe,KAAqB;CAQlD,OAPe,UAAU;EACvB,UAAU;EACV,MAAM,OAAO,KAAK,GAAG;EACrB,QAAQ;EACR,SAAS,EAAE,IAAI,IAAO;CACxB,CAEY,
|
|
1
|
+
{"version":3,"file":"compileTailwindCss.js","names":["resolveProps","pruneVars"],"sources":["../../src/utils/compileTailwindCss.ts"],"sourcesContent":["import postcss from 'postcss'\nimport tailwindcssPostcss from '@tailwindcss/postcss'\nimport postcssCalc from 'postcss-calc'\nimport safeParser from 'postcss-safe-parser'\nimport { transform } from 'lightningcss'\nimport resolveProps from '../plugins/postcss/resolveProps.ts'\nimport pruneVars from '../plugins/postcss/pruneVars.ts'\nimport { tailwindCleanup } from '../plugins/postcss/tailwindCleanup.ts'\nimport { mergeMediaQueries } from '../plugins/postcss/mergeMediaQueries.ts'\nimport { quoteFontFamilies } from '../plugins/postcss/quoteFontFamilies.ts'\nimport { resolveMaizzleImports } from '../plugins/postcss/resolveMaizzleImports.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\nexport function createTailwindProcessor(config: MaizzleConfig) {\n return postcss([\n // Must run before @tailwindcss/postcss so it sees absolute import paths\n resolveMaizzleImports(),\n tailwindcssPostcss({\n base: config.css?.base,\n transformAssetUrls: false,\n optimize: false,\n }),\n resolveProps(),\n postcssCalc({}),\n pruneVars(),\n ])\n}\n\nexport function lowerCssSyntax(css: string): string {\n const result = transform({\n filename: 'email.css',\n code: Buffer.from(css),\n minify: false,\n targets: { ie: 4 << 5 },\n })\n\n return result.code.toString()\n}\n\nexport async function optimizeTailwindCss(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 * Compile a Tailwind CSS source string into final email-safe CSS:\n * runs @tailwindcss/postcss, lowers modern syntax via lightningcss,\n * then applies cleanup + media-query merging.\n */\nexport async function compileTailwindCss(\n cssInput: string,\n config: MaizzleConfig,\n from: string,\n): Promise<string> {\n const processor = createTailwindProcessor(config)\n const result = await processor.process(cssInput, { from, parser: safeParser })\n const lowered = lowerCssSyntax(result.css)\n return optimizeTailwindCss(lowered, config)\n}\n"],"mappings":";;;;;;;;;;;;AAaA,SAAgB,wBAAwB,QAAuB;CAC7D,OAAO,QAAQ;EAEb,sBAAsB;EACtB,mBAAmB;GACjB,MAAM,OAAO,KAAK;GAClB,oBAAoB;GACpB,UAAU;EACZ,CAAC;EACDA,qBAAa;EACb,YAAY,CAAC,CAAC;EACdC,kBAAU;CACZ,CAAC;AACH;AAEA,SAAgB,eAAe,KAAqB;CAQlD,OAPe,UAAU;EACvB,UAAU;EACV,MAAM,OAAO,KAAK,GAAG;EACrB,QAAQ;EACR,SAAS,EAAE,IAAI,IAAO;CACxB,CAEY,CAAC,CAAC,KAAK,SAAS;AAC9B;AAEA,eAAsB,oBAAoB,KAAa,QAAwC;CAC7F,MAAM,UAA4B,CAAC,GAAG,gBAAgB,MAAM,GAAG,kBAAkB,CAAC;CAElF,MAAM,cAAc,kBAAkB,MAAM;CAC5C,IAAI,aAAa,QAAQ,KAAK,WAAW;CAIzC,QAAO,MAFc,QAAQ,OAAO,CAAC,CAAC,QAAQ,KAAK,EAAE,MAAM,KAAA,EAAU,CAAC,EAAA,CAExD;AAChB;;;;;;AAOA,eAAsB,mBACpB,UACA,QACA,MACiB;CAIjB,OAAO,oBADS,gBAAe,MAFb,wBAAwB,MACb,CAAC,CAAC,QAAQ,UAAU;EAAE;EAAM,QAAQ;CAAW,CAAC,EAAA,CACvC,GACL,GAAG,MAAM;AAC5C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"componentSources.js","names":[],"sources":["../../src/utils/componentSources.ts"],"sourcesContent":["import { resolve, relative } from 'node:path'\nimport type { ComponentSource } from '../types/config.ts'\n\n/**\n * Internal representation of a component source after defaults are\n * resolved and `path` is made absolute. Always carries the same shape\n * regardless of which user-facing form produced it.\n */\nexport interface NormalizedComponentSource {\n /** Absolute directory path. */\n path: string\n /**\n * Custom prefix prepended to resolved component names.\n * - `undefined` → use folder-name namespace (the `directoryAsNamespace`\n * behavior of unplugin-vue-components).\n * - `''` (empty string) → no prefix at all; use the bare filename.\n * - any other string → exact prefix.\n */\n prefix?: string\n /** Include intermediate subfolder names in the resolved name. */\n pathPrefix: boolean\n}\n\n/**\n * Normalize a user-supplied `components.source` value into an array\n * of absolute, fully-defaulted entries.\n */\nexport function normalizeComponentSources(\n sources: ComponentSource | ComponentSource[] | undefined,\n cwd: string,\n): NormalizedComponentSource[] {\n if (!sources) return []\n const list = Array.isArray(sources) ? sources : [sources]\n return list.map((s) => {\n if (typeof s === 'string') {\n return { path: resolve(cwd, s), prefix: undefined, pathPrefix: true }\n }\n return {\n path: resolve(cwd, s.path),\n prefix: s.prefix,\n pathPrefix: s.pathPrefix ?? true,\n }\n })\n}\n\nfunction pascalCase(s: string): string {\n return s\n .replace(/[-_\\s]+(.)/g, (_, c) => c.toUpperCase())\n .replace(/^(.)/, c => c.toUpperCase())\n}\n\nexport interface ComponentNameOptions {\n /** Absolute path to the component file. */\n filePath: string\n /** Absolute path to the dir root the component was discovered under. */\n dirRoot: string\n /** See {@link NormalizedComponentSource.prefix}. */\n prefix?: string\n /** See {@link NormalizedComponentSource.pathPrefix}. */\n pathPrefix: boolean\n}\n\n/**\n * Compute the component name unplugin-vue-components would assign for a\n * given file under a given dir, mirroring the plugin's\n * `directoryAsNamespace: true` + `collapseSamePrefixes: true` behavior\n * and layering custom-prefix sources on top.\n *\n * Used both at render time (to register a custom resolver) and at lint\n * time (so the linter can follow component graph correctly).\n */\nexport function componentNameFromPath(opts: ComponentNameOptions): string {\n const { filePath, dirRoot, prefix, pathPrefix } = opts\n\n const rel = relative(dirRoot, filePath).replace(/\\\\/g, '/')\n const noExt = rel.replace(/\\.(vue|md)$/, '')\n const segments = noExt.split('/').map(pascalCase)\n const fileName = segments.pop() ?? ''\n\n if (prefix !== undefined) {\n const folderPart = pathPrefix ? segments.join('') : ''\n const stripped = prefix && fileName.startsWith(prefix)\n ? fileName.slice(prefix.length)\n : fileName\n return prefix + folderPart + stripped\n }\n\n const folderPart = segments.join('')\n if (folderPart && fileName.startsWith(folderPart)) {\n return fileName\n }\n return folderPart + fileName\n}\n"],"mappings":";;;;;;AA2BA,SAAgB,0BACd,SACA,KAC6B;CAC7B,IAAI,CAAC,SAAS,OAAO,CAAC;CAEtB,QADa,MAAM,QAAQ,OAAO,IAAI,UAAU,CAAC,OAAO,
|
|
1
|
+
{"version":3,"file":"componentSources.js","names":[],"sources":["../../src/utils/componentSources.ts"],"sourcesContent":["import { resolve, relative } from 'node:path'\nimport type { ComponentSource } from '../types/config.ts'\n\n/**\n * Internal representation of a component source after defaults are\n * resolved and `path` is made absolute. Always carries the same shape\n * regardless of which user-facing form produced it.\n */\nexport interface NormalizedComponentSource {\n /** Absolute directory path. */\n path: string\n /**\n * Custom prefix prepended to resolved component names.\n * - `undefined` → use folder-name namespace (the `directoryAsNamespace`\n * behavior of unplugin-vue-components).\n * - `''` (empty string) → no prefix at all; use the bare filename.\n * - any other string → exact prefix.\n */\n prefix?: string\n /** Include intermediate subfolder names in the resolved name. */\n pathPrefix: boolean\n}\n\n/**\n * Normalize a user-supplied `components.source` value into an array\n * of absolute, fully-defaulted entries.\n */\nexport function normalizeComponentSources(\n sources: ComponentSource | ComponentSource[] | undefined,\n cwd: string,\n): NormalizedComponentSource[] {\n if (!sources) return []\n const list = Array.isArray(sources) ? sources : [sources]\n return list.map((s) => {\n if (typeof s === 'string') {\n return { path: resolve(cwd, s), prefix: undefined, pathPrefix: true }\n }\n return {\n path: resolve(cwd, s.path),\n prefix: s.prefix,\n pathPrefix: s.pathPrefix ?? true,\n }\n })\n}\n\nfunction pascalCase(s: string): string {\n return s\n .replace(/[-_\\s]+(.)/g, (_, c) => c.toUpperCase())\n .replace(/^(.)/, c => c.toUpperCase())\n}\n\nexport interface ComponentNameOptions {\n /** Absolute path to the component file. */\n filePath: string\n /** Absolute path to the dir root the component was discovered under. */\n dirRoot: string\n /** See {@link NormalizedComponentSource.prefix}. */\n prefix?: string\n /** See {@link NormalizedComponentSource.pathPrefix}. */\n pathPrefix: boolean\n}\n\n/**\n * Compute the component name unplugin-vue-components would assign for a\n * given file under a given dir, mirroring the plugin's\n * `directoryAsNamespace: true` + `collapseSamePrefixes: true` behavior\n * and layering custom-prefix sources on top.\n *\n * Used both at render time (to register a custom resolver) and at lint\n * time (so the linter can follow component graph correctly).\n */\nexport function componentNameFromPath(opts: ComponentNameOptions): string {\n const { filePath, dirRoot, prefix, pathPrefix } = opts\n\n const rel = relative(dirRoot, filePath).replace(/\\\\/g, '/')\n const noExt = rel.replace(/\\.(vue|md)$/, '')\n const segments = noExt.split('/').map(pascalCase)\n const fileName = segments.pop() ?? ''\n\n if (prefix !== undefined) {\n const folderPart = pathPrefix ? segments.join('') : ''\n const stripped = prefix && fileName.startsWith(prefix)\n ? fileName.slice(prefix.length)\n : fileName\n return prefix + folderPart + stripped\n }\n\n const folderPart = segments.join('')\n if (folderPart && fileName.startsWith(folderPart)) {\n return fileName\n }\n return folderPart + fileName\n}\n"],"mappings":";;;;;;AA2BA,SAAgB,0BACd,SACA,KAC6B;CAC7B,IAAI,CAAC,SAAS,OAAO,CAAC;CAEtB,QADa,MAAM,QAAQ,OAAO,IAAI,UAAU,CAAC,OAAO,EAAA,CAC5C,KAAK,MAAM;EACrB,IAAI,OAAO,MAAM,UACf,OAAO;GAAE,MAAM,QAAQ,KAAK,CAAC;GAAG,QAAQ,KAAA;GAAW,YAAY;EAAK;EAEtE,OAAO;GACL,MAAM,QAAQ,KAAK,EAAE,IAAI;GACzB,QAAQ,EAAE;GACV,YAAY,EAAE,cAAc;EAC9B;CACF,CAAC;AACH;AAEA,SAAS,WAAW,GAAmB;CACrC,OAAO,EACJ,QAAQ,gBAAgB,GAAG,MAAM,EAAE,YAAY,CAAC,CAAC,CACjD,QAAQ,SAAQ,MAAK,EAAE,YAAY,CAAC;AACzC;;;;;;;;;;AAsBA,SAAgB,sBAAsB,MAAoC;CACxE,MAAM,EAAE,UAAU,SAAS,QAAQ,eAAe;CAIlD,MAAM,WAFM,SAAS,SAAS,QAAQ,CAAC,CAAC,QAAQ,OAAO,GACvC,CAAC,CAAC,QAAQ,eAAe,EACpB,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,UAAU;CAChD,MAAM,WAAW,SAAS,IAAI,KAAK;CAEnC,IAAI,WAAW,KAAA,GAAW;EACxB,MAAM,aAAa,aAAa,SAAS,KAAK,EAAE,IAAI;EACpD,MAAM,WAAW,UAAU,SAAS,WAAW,MAAM,IACjD,SAAS,MAAM,OAAO,MAAM,IAC5B;EACJ,OAAO,SAAS,aAAa;CAC/B;CAEA,MAAM,aAAa,SAAS,KAAK,EAAE;CACnC,IAAI,cAAc,SAAS,WAAW,UAAU,GAC9C,OAAO;CAET,OAAO,aAAa;AACtB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cssBox.d.ts","names":[],"sources":["../../src/utils/cssBox.ts"],"mappings":";;;cAEa,gBAAA,EAAgB,GAAA;;AAA7B;;;;iBAOgB,UAAA,CAAW,KAAa;AAAxC;;;;AAAwC;
|
|
1
|
+
{"version":3,"file":"cssBox.d.ts","names":[],"sources":["../../src/utils/cssBox.ts"],"mappings":";;;cAEa,gBAAA,EAAgB,GAAA;;AAA7B;;;;iBAOgB,UAAA,CAAW,KAAa;AAAxC;;;;AAAwC;AAexC;;AAfA,iBAegB,cAAA,CAAe,KAAA;EAAkB,IAAA;EAAe,KAAA;AAAA;;;AAAK;AAerE;iBAAgB,mBAAA,CAAoB,IAAU,EAAJ,IAAI;;;AAAA;AA8B9C;;;iBAAgB,sBAAA,CAAuB,KAAa;AAAA;AAepD;;;;AAfoD,iBAepC,kBAAA,CAAmB,IAAU,EAAJ,IAAI"}
|
package/dist/utils/cssBox.js
CHANGED
|
@@ -9,13 +9,8 @@ function lengthToPx(value) {
|
|
|
9
9
|
const m = value.trim().match(/^([\d.]+)(px|rem|em|pt)?$/i);
|
|
10
10
|
if (!m) return null;
|
|
11
11
|
const n = parseFloat(m[1]);
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
case "rem":
|
|
15
|
-
case "em": return n * 16;
|
|
16
|
-
case "pt": return n * 1.333;
|
|
17
|
-
default: return null;
|
|
18
|
-
}
|
|
12
|
+
const unit = (m[2] || "px").toLowerCase();
|
|
13
|
+
return n * (unit === "rem" || unit === "em" ? 16 : unit === "pt" ? 1.333 : 1);
|
|
19
14
|
}
|
|
20
15
|
/**
|
|
21
16
|
* Expand a 1-4 token CSS shorthand (T R B L) into a left/right pair:
|