diff --git a/packages/jsx-email/src/renderer/raw.ts b/packages/jsx-email/src/renderer/raw.ts index 7721c2264..a19c46c76 100644 --- a/packages/jsx-email/src/renderer/raw.ts +++ b/packages/jsx-email/src/renderer/raw.ts @@ -1,3 +1,5 @@ +import type { Root } from 'hast'; + const START_TAG = '__COMMENT_START'; const END_TAG = '__COMMENT_END'; export function escapeForRawComponent(input: string): string { @@ -10,3 +12,129 @@ export function unescapeForRawComponent(input: string): string { .replace(new RegExp(START_TAG, 'g'), ''); } + +// Normalize corrupted MSO conditional closers that can occur when nested +// comment boundaries abut the `` sequence (e.g. ``). +export function normalizeMsoConditionalClosers(input: string): string { + return input.replace(//g, ''); +} + +// Shared pattern for a `` wrapper, +// also matching an encoded comment opener and a corrupted `` closer. +export const RAW_WRAPPER_RE = + /]*?>\s*(?:\s*<\/jsx-email-raw(?:--)?\s*>/g; + +// Return a rehype plugin that replaces +// with a HAST "raw" node containing the unescaped payload. This avoids nesting +// HTML comments when appears inside Outlook conditional comments. +export const getRawPlugin = async () => { + const { visit } = await import('unist-util-visit'); + + return function rawPlugin() { + return function transform(tree: Root) { + visit(tree, 'element', (node: any, index: number | null, parent: any) => { + if (!parent || index == null) return; + + // 1) Lift comment payloads into literal HTML + if (node.tagName === 'jsx-email-raw') { + const child = node.children && node.children[0]; + if (!child) return; + + if (child.type === 'comment') { + const payload = String(child.value ?? ''); + const html = unescapeForRawComponent(payload); + parent.children.splice(index, 1, { type: 'raw', value: html }); + } + return; + } + + // 2) Replace wrapper with raw nodes that + // represent the actual Outlook conditional bytes. Handle + // both the single-comment form () + // and the downlevel-revealed form (...). + if (node.tagName === 'jsx-email-cond') { + // NOTE: keep transformation logic local to this plugin. + const children = Array.isArray(node.children) ? (node.children as any[]) : []; + if (!children.length) return; + + // Prefer transforming AST nodes directly over string parsing. + + // Replace the wrapper element with: opening comment, transformed + // children (with lifted), and closing comment. + const transformRaw = (nodes: any[]): any[] => + nodes.flatMap((n) => { + if (n.type === 'element' && n.tagName === 'jsx-email-raw') { + const first = n.children && n.children[0]; + const payload = typeof first?.value === 'string' ? first.value : ''; + return [{ type: 'raw', value: unescapeForRawComponent(payload) }]; + } + if (n.children && Array.isArray(n.children)) { + n.children = transformRaw(n.children); + } + return [n]; + }); + + const first = children[0]; + const last = children[children.length - 1]; + // Downlevel-revealed form produces 2 comment siblings around real + // element/text children: `` … ``. + // Preserve the exact serializer bytes by keeping the original + // comment nodes and only transforming any nested + // elements found between them. + if (first?.type === 'comment' && last?.type === 'comment' && children.length > 1) { + const middle = transformRaw(children.slice(1, -1)); + parent.children.splice(index, 1, first, ...middle, last); + return; + } + + // Single-comment form packs everything into a single comment node: + // ``. Split it into raw nodes so that + // nested wrappers can be safely inlined. + if (first?.type === 'comment') { + const value = String(first.value ?? ''); + const openIdx = value.indexOf(']>'); + const closeIdx = value.lastIndexOf('= 0 && closeIdx > openIdx)) { + parent.children.splice(index, 1, ...transformRaw(children)); + return; + } + + // Construct opening marker comment bytes + const openRaw = openIdx >= 0 ? ` occurrences inside the + // single-comment payload, being tolerant of a corrupted element closer like + // "" that some serializers may emit when adjacent to comment ends. + innerHtml = innerHtml.replace(RAW_WRAPPER_RE, (_m: string, p1: string) => + unescapeForRawComponent(p1) + ); + + // Include any middle child nodes (downlevel-revealed form produces real nodes) + parent.children.splice( + index, + 1, + { type: 'raw', value: openRaw }, + { type: 'raw', value: innerHtml }, + ...transformRaw(children.slice(1, -1)), + { type: 'raw', value: '' } + ); + return; + } + + // Fallback: drop the wrapper and keep transformed children as-is. + parent.children.splice(index, 1, ...transformRaw(children)); + } + }); + }; + }; +}; diff --git a/packages/jsx-email/src/renderer/render.ts b/packages/jsx-email/src/renderer/render.ts index fe2787eab..cacb482ed 100644 --- a/packages/jsx-email/src/renderer/render.ts +++ b/packages/jsx-email/src/renderer/render.ts @@ -6,7 +6,12 @@ import type { PlainTextOptions, RenderOptions } from '../types.js'; import { jsxToString } from './jsx-to-string.js'; import { getMovePlugin } from './move-style.js'; -import { unescapeForRawComponent } from './raw.js'; +import { + RAW_WRAPPER_RE, + getRawPlugin, + normalizeMsoConditionalClosers, + unescapeForRawComponent +} from './raw.js'; export const jsxEmailTags = ['jsx-email-cond']; @@ -74,13 +79,14 @@ const processHtml = async (config: JsxEmailConfig, html: string) => { const docType = ''; const movePlugin = await getMovePlugin(); + const rawPlugin = await getRawPlugin(); const settings = { emitParseErrors: true }; - const reJsxTags = new RegExp(`<[/]?(${jsxEmailTags.join('|')})>`, 'g'); // @ts-ignore: This is perfectly valid, see here: https://www.npmjs.com/package/rehype#examples const processor = rehype().data('settings', settings); processor.use(movePlugin); + processor.use(rawPlugin); await callProcessHook({ config, processor }); const doc = await processor @@ -92,12 +98,29 @@ const processHtml = async (config: JsxEmailConfig, html: string) => { }) .process(html); - let result = docType + String(doc).replace('', '').replace('', ''); - - result = result.replace(reJsxTags, ''); - result = result.replace(/<\/jsx-email-raw>/gs, (_, p1) => - unescapeForRawComponent(p1) + const result = docType + String(doc).replace('', '').replace('', ''); + try { + if (process.env.DEBUG_REHYPE && result.includes('`, 'g'); + const reRawWrapper = RAW_WRAPPER_RE; + // Handle a corrupted closer where the tag is eaten and the + // comment terminator abuts the MSO closer: ``. + const reRawBeforeEndif = + /]*?>\s*(?:[\s\r\n]*(?=(?:)/g; + const unwrapped = normalizeMsoConditionalClosers( + result + .replace(reJsxTags, '') + .replace(reRawWrapper, (_m, p1) => unescapeForRawComponent(p1)) + .replace(reRawBeforeEndif, (_m, p1) => unescapeForRawComponent(p1)) ); - - return result; + return unwrapped; }; diff --git a/packages/jsx-email/test/conditional-closing.test.tsx b/packages/jsx-email/test/conditional-closing.test.tsx new file mode 100644 index 000000000..a40e1c29a --- /dev/null +++ b/packages/jsx-email/test/conditional-closing.test.tsx @@ -0,0 +1,73 @@ +// @ts-ignore +import React from 'react'; + +import { render } from '../src/renderer/render.js'; +import { Conditional } from '../src/index.js'; + +/** + * Repro for issue #316: MSO conditional closer is malformed in body content. + * + * This test mirrors the structure reported by the user: + * - a modern-clients block wrapped in + * - followed by an MSO-only block wrapped in + * both as siblings within table rows/cells. + * + * Expected: the MSO block closes with `` and no extra hyphens. + */ +describe(' closer integrity', async () => { + beforeEach(() => { + vi.restoreAllMocks(); + vi.resetModules(); + }); + + it('emits a well-formed for mso blocks placed in table cells', async () => { + const fragment = ( + + + + + + + + + modern + + + + + + + + + + + + + mso + + + + + + + + + ); + + const html = await render(fragment, { pretty: true }); + + // Guard against the known-bad tail observed in .eml outputs: + expect(html).not.toContain(''); + + // Verify that our MSO block uses a valid closer. + // We limit the search to our block by anchoring on a unique attribute. + const start = html.indexOf(''); + }); +}); diff --git a/packages/jsx-email/test/conditional-mso-raw-closing.test.tsx b/packages/jsx-email/test/conditional-mso-raw-closing.test.tsx new file mode 100644 index 000000000..3739d55fa --- /dev/null +++ b/packages/jsx-email/test/conditional-mso-raw-closing.test.tsx @@ -0,0 +1,58 @@ +// @ts-ignore +import React from 'react'; + +import { render } from '../src/renderer/render.js'; +import { Conditional, Raw } from '../src/index.js'; + +describe(' + interaction', async () => { + beforeEach(() => { + vi.restoreAllMocks(); + vi.resetModules(); + }); + + it('does not corrupt the closer when Raw is present inside the MSO block', async () => { + const fragment = ( + + + + + + + + + + {/* Minimal VML corner via Raw (no comments inside) */} + ' + } + /> + + + + ' + } + /> + + + + + + + + + + ); + + const html = await render(fragment, { pretty: true }); + + // Known-bad form observed in issue reports + expect(html).not.toContain(''); + + // Valid closer must exist for the MSO block + const idx = html.indexOf('', html.indexOf(' closer', async () => { + const fragment = ( + + ' + } + /> + + ); + + const html = await render(fragment, { minify: false, pretty: false }); + + // Should render Outlook conditional markers + expect(html).toContain(''); + + // Raw payload should be inlined as literal HTML (no synthetic wrappers left) + expect(html).toContain(' basic set 1`] = ` "reject": [Function], "resolve": [Function], }, - "template": "[{{time}}] [34mjsx-email[39m [1m∵ root/minify[22m {{level}} ", + "template": "[{{time}}] jsx-email ∵ root/minify {{level}} ", "time": [Function], }, "ready": { @@ -38,7 +38,7 @@ exports[`defineConfig > basic set 1`] = ` "WARN": 3, }, }, - "name": "dot-log:[1m∵ root/minify[22m", + "name": "dot-log:∵ root/minify", "options": { "factory": LogFactory { "options": { @@ -49,7 +49,7 @@ exports[`defineConfig > basic set 1`] = ` "reject": [Function], "resolve": [Function], }, - "template": "[{{time}}] [34mjsx-email[39m [1m∵ root/minify[22m {{level}} ", + "template": "[{{time}}] jsx-email ∵ root/minify {{level}} ", "time": [Function], }, "ready": { @@ -67,9 +67,9 @@ exports[`defineConfig > basic set 1`] = ` "WARN": 3, }, }, - "id": "dot-log:[1m∵ root/minify[22m", + "id": "dot-log:∵ root/minify", "level": "info", - "name": "dot-log:[1m∵ root/minify[22m", + "name": "dot-log:∵ root/minify", "prefix": undefined, }, "trace": [Function], @@ -112,7 +112,7 @@ exports[`defineConfig > de-dupe plugins 1`] = ` "reject": [Function], "resolve": [Function], }, - "template": "[{{time}}] [34mjsx-email[39m [1m∵ batman[22m {{level}} ", + "template": "[{{time}}] jsx-email ∵ batman {{level}} ", "time": [Function], }, "ready": { @@ -130,7 +130,7 @@ exports[`defineConfig > de-dupe plugins 1`] = ` "WARN": 3, }, }, - "name": "dot-log:[1m∵ batman[22m", + "name": "dot-log:∵ batman", "options": { "factory": LogFactory { "options": { @@ -141,7 +141,7 @@ exports[`defineConfig > de-dupe plugins 1`] = ` "reject": [Function], "resolve": [Function], }, - "template": "[{{time}}] [34mjsx-email[39m [1m∵ batman[22m {{level}} ", + "template": "[{{time}}] jsx-email ∵ batman {{level}} ", "time": [Function], }, "ready": { @@ -159,9 +159,9 @@ exports[`defineConfig > de-dupe plugins 1`] = ` "WARN": 3, }, }, - "id": "dot-log:[1m∵ batman[22m", + "id": "dot-log:∵ batman", "level": "info", - "name": "dot-log:[1m∵ batman[22m", + "name": "dot-log:∵ batman", "prefix": undefined, }, "trace": [Function], @@ -218,7 +218,7 @@ exports[`defineConfig > minify and pretty 1`] = ` "reject": [Function], "resolve": [Function], }, - "template": "[{{time}}] [34mjsx-email[39m [1m∵ root/minify[22m {{level}} ", + "template": "[{{time}}] jsx-email ∵ root/minify {{level}} ", "time": [Function], }, "ready": { @@ -236,7 +236,7 @@ exports[`defineConfig > minify and pretty 1`] = ` "WARN": 3, }, }, - "name": "dot-log:[1m∵ root/minify[22m", + "name": "dot-log:∵ root/minify", "options": { "factory": LogFactory { "options": { @@ -247,7 +247,7 @@ exports[`defineConfig > minify and pretty 1`] = ` "reject": [Function], "resolve": [Function], }, - "template": "[{{time}}] [34mjsx-email[39m [1m∵ root/minify[22m {{level}} ", + "template": "[{{time}}] jsx-email ∵ root/minify {{level}} ", "time": [Function], }, "ready": { @@ -265,9 +265,9 @@ exports[`defineConfig > minify and pretty 1`] = ` "WARN": 3, }, }, - "id": "dot-log:[1m∵ root/minify[22m", + "id": "dot-log:∵ root/minify", "level": "info", - "name": "dot-log:[1m∵ root/minify[22m", + "name": "dot-log:∵ root/minify", "prefix": undefined, }, "trace": [Function], @@ -295,7 +295,7 @@ exports[`defineConfig > minify and pretty 1`] = ` "reject": [Function], "resolve": [Function], }, - "template": "[{{time}}] [34mjsx-email[39m [1m∵ root/pretty[22m {{level}} ", + "template": "[{{time}}] jsx-email ∵ root/pretty {{level}} ", "time": [Function], }, "ready": { @@ -313,7 +313,7 @@ exports[`defineConfig > minify and pretty 1`] = ` "WARN": 3, }, }, - "name": "dot-log:[1m∵ root/pretty[22m", + "name": "dot-log:∵ root/pretty", "options": { "factory": LogFactory { "options": { @@ -324,7 +324,7 @@ exports[`defineConfig > minify and pretty 1`] = ` "reject": [Function], "resolve": [Function], }, - "template": "[{{time}}] [34mjsx-email[39m [1m∵ root/pretty[22m {{level}} ", + "template": "[{{time}}] jsx-email ∵ root/pretty {{level}} ", "time": [Function], }, "ready": { @@ -342,9 +342,9 @@ exports[`defineConfig > minify and pretty 1`] = ` "WARN": 3, }, }, - "id": "dot-log:[1m∵ root/pretty[22m", + "id": "dot-log:∵ root/pretty", "level": "info", - "name": "dot-log:[1m∵ root/pretty[22m", + "name": "dot-log:∵ root/pretty", "prefix": undefined, }, "trace": [Function], diff --git a/packages/jsx-email/test/config/load/dotdir/.snapshots/load-dotdir.test.ts.snap b/packages/jsx-email/test/config/load/dotdir/.snapshots/load-dotdir.test.ts.snap index 21bc3ab6d..6429300e8 100644 --- a/packages/jsx-email/test/config/load/dotdir/.snapshots/load-dotdir.test.ts.snap +++ b/packages/jsx-email/test/config/load/dotdir/.snapshots/load-dotdir.test.ts.snap @@ -20,7 +20,7 @@ exports[`loadConfig → dotdir > loadConfig 1`] = ` "reject": [Function], "resolve": [Function], }, - "template": "[{{time}}] [34mjsx-email[39m [1m∵ root/minify[22m {{level}} ", + "template": "[{{time}}] jsx-email ∵ root/minify {{level}} ", "time": [Function], }, "ready": { @@ -38,7 +38,7 @@ exports[`loadConfig → dotdir > loadConfig 1`] = ` "WARN": 3, }, }, - "name": "dot-log:[1m∵ root/minify[22m", + "name": "dot-log:∵ root/minify", "options": { "factory": LogFactory { "options": { @@ -49,7 +49,7 @@ exports[`loadConfig → dotdir > loadConfig 1`] = ` "reject": [Function], "resolve": [Function], }, - "template": "[{{time}}] [34mjsx-email[39m [1m∵ root/minify[22m {{level}} ", + "template": "[{{time}}] jsx-email ∵ root/minify {{level}} ", "time": [Function], }, "ready": { @@ -67,9 +67,9 @@ exports[`loadConfig → dotdir > loadConfig 1`] = ` "WARN": 3, }, }, - "id": "dot-log:[1m∵ root/minify[22m", + "id": "dot-log:∵ root/minify", "level": "info", - "name": "dot-log:[1m∵ root/minify[22m", + "name": "dot-log:∵ root/minify", "prefix": undefined, }, "trace": [Function], diff --git a/packages/jsx-email/test/config/load/mjs-json/.snapshots/load-mjs-json.test.ts.snap b/packages/jsx-email/test/config/load/mjs-json/.snapshots/load-mjs-json.test.ts.snap index 074e6ace5..c72ac4768 100644 --- a/packages/jsx-email/test/config/load/mjs-json/.snapshots/load-mjs-json.test.ts.snap +++ b/packages/jsx-email/test/config/load/mjs-json/.snapshots/load-mjs-json.test.ts.snap @@ -20,7 +20,7 @@ exports[`loadConfig → mjs-json > loadConfig 1`] = ` "reject": [Function], "resolve": [Function], }, - "template": "[{{time}}] [34mjsx-email[39m [1m∵ root/minify[22m {{level}} ", + "template": "[{{time}}] jsx-email ∵ root/minify {{level}} ", "time": [Function], }, "ready": { @@ -38,7 +38,7 @@ exports[`loadConfig → mjs-json > loadConfig 1`] = ` "WARN": 3, }, }, - "name": "dot-log:[1m∵ root/minify[22m", + "name": "dot-log:∵ root/minify", "options": { "factory": LogFactory { "options": { @@ -49,7 +49,7 @@ exports[`loadConfig → mjs-json > loadConfig 1`] = ` "reject": [Function], "resolve": [Function], }, - "template": "[{{time}}] [34mjsx-email[39m [1m∵ root/minify[22m {{level}} ", + "template": "[{{time}}] jsx-email ∵ root/minify {{level}} ", "time": [Function], }, "ready": { @@ -67,9 +67,9 @@ exports[`loadConfig → mjs-json > loadConfig 1`] = ` "WARN": 3, }, }, - "id": "dot-log:[1m∵ root/minify[22m", + "id": "dot-log:∵ root/minify", "level": "info", - "name": "dot-log:[1m∵ root/minify[22m", + "name": "dot-log:∵ root/minify", "prefix": undefined, }, "trace": [Function], diff --git a/packages/jsx-email/test/config/load/mjs/.snapshots/load-mjs.test.ts.snap b/packages/jsx-email/test/config/load/mjs/.snapshots/load-mjs.test.ts.snap index 8f43203e2..a07fcf2ca 100644 --- a/packages/jsx-email/test/config/load/mjs/.snapshots/load-mjs.test.ts.snap +++ b/packages/jsx-email/test/config/load/mjs/.snapshots/load-mjs.test.ts.snap @@ -20,7 +20,7 @@ exports[`loadConfig → mjs > loadConfig 1`] = ` "reject": [Function], "resolve": [Function], }, - "template": "[{{time}}] [34mjsx-email[39m [1m∵ root/minify[22m {{level}} ", + "template": "[{{time}}] jsx-email ∵ root/minify {{level}} ", "time": [Function], }, "ready": { @@ -38,7 +38,7 @@ exports[`loadConfig → mjs > loadConfig 1`] = ` "WARN": 3, }, }, - "name": "dot-log:[1m∵ root/minify[22m", + "name": "dot-log:∵ root/minify", "options": { "factory": LogFactory { "options": { @@ -49,7 +49,7 @@ exports[`loadConfig → mjs > loadConfig 1`] = ` "reject": [Function], "resolve": [Function], }, - "template": "[{{time}}] [34mjsx-email[39m [1m∵ root/minify[22m {{level}} ", + "template": "[{{time}}] jsx-email ∵ root/minify {{level}} ", "time": [Function], }, "ready": { @@ -67,9 +67,9 @@ exports[`loadConfig → mjs > loadConfig 1`] = ` "WARN": 3, }, }, - "id": "dot-log:[1m∵ root/minify[22m", + "id": "dot-log:∵ root/minify", "level": "info", - "name": "dot-log:[1m∵ root/minify[22m", + "name": "dot-log:∵ root/minify", "prefix": undefined, }, "trace": [Function], diff --git a/packages/jsx-email/test/config/load/parent-dir/child-dir/.snapshots/parent-dir.test.ts.snap b/packages/jsx-email/test/config/load/parent-dir/child-dir/.snapshots/parent-dir.test.ts.snap index 72029a472..50b46601a 100644 --- a/packages/jsx-email/test/config/load/parent-dir/child-dir/.snapshots/parent-dir.test.ts.snap +++ b/packages/jsx-email/test/config/load/parent-dir/child-dir/.snapshots/parent-dir.test.ts.snap @@ -20,7 +20,7 @@ exports[`loadConfig → parent dir > loadConfig 1`] = ` "reject": [Function], "resolve": [Function], }, - "template": "[{{time}}] [34mjsx-email[39m [1m∵ root/minify[22m {{level}} ", + "template": "[{{time}}] jsx-email ∵ root/minify {{level}} ", "time": [Function], }, "ready": { @@ -38,7 +38,7 @@ exports[`loadConfig → parent dir > loadConfig 1`] = ` "WARN": 3, }, }, - "name": "dot-log:[1m∵ root/minify[22m", + "name": "dot-log:∵ root/minify", "options": { "factory": LogFactory { "options": { @@ -49,7 +49,7 @@ exports[`loadConfig → parent dir > loadConfig 1`] = ` "reject": [Function], "resolve": [Function], }, - "template": "[{{time}}] [34mjsx-email[39m [1m∵ root/minify[22m {{level}} ", + "template": "[{{time}}] jsx-email ∵ root/minify {{level}} ", "time": [Function], }, "ready": { @@ -67,9 +67,9 @@ exports[`loadConfig → parent dir > loadConfig 1`] = ` "WARN": 3, }, }, - "id": "dot-log:[1m∵ root/minify[22m", + "id": "dot-log:∵ root/minify", "level": "info", - "name": "dot-log:[1m∵ root/minify[22m", + "name": "dot-log:∵ root/minify", "prefix": undefined, }, "trace": [Function], diff --git a/packages/plugin-inline/package.json b/packages/plugin-inline/package.json index 2af541407..574599d24 100644 --- a/packages/plugin-inline/package.json +++ b/packages/plugin-inline/package.json @@ -14,15 +14,18 @@ "author": "Andrew Powell ", "homepage": "https://jsx.email/", "main": "./dist/commonjs/index.js", + "module": "./dist/esm/index.js", "type": "module", "exports": { "./package.json": "./package.json", ".": { "import": { + "source": "./src/index.ts", "types": "./dist/esm/index.d.ts", "default": "./dist/esm/index.js" }, "require": { + "source": "./src/index.ts", "types": "./dist/commonjs/index.d.ts", "default": "./dist/commonjs/index.js" } diff --git a/packages/plugin-minify/package.json b/packages/plugin-minify/package.json index cde7be798..bc51cad7b 100644 --- a/packages/plugin-minify/package.json +++ b/packages/plugin-minify/package.json @@ -14,15 +14,18 @@ "author": "Andrew Powell ", "homepage": "https://jsx.email/", "main": "./dist/commonjs/index.js", + "module": "./dist/esm/index.js", "type": "module", "exports": { "./package.json": "./package.json", ".": { "import": { + "source": "./src/index.ts", "types": "./dist/esm/index.d.ts", "default": "./dist/esm/index.js" }, "require": { + "source": "./src/index.ts", "types": "./dist/commonjs/index.d.ts", "default": "./dist/commonjs/index.js" } diff --git a/packages/plugin-pretty/package.json b/packages/plugin-pretty/package.json index 18a5629f4..c9b306be4 100644 --- a/packages/plugin-pretty/package.json +++ b/packages/plugin-pretty/package.json @@ -14,15 +14,18 @@ "author": "Andrew Powell ", "homepage": "https://jsx.email/", "main": "./dist/commonjs/index.js", + "module": "./dist/esm/index.js", "type": "module", "exports": { "./package.json": "./package.json", ".": { "import": { + "source": "./src/index.ts", "types": "./dist/esm/index.d.ts", "default": "./dist/esm/index.js" }, "require": { + "source": "./src/index.ts", "types": "./dist/commonjs/index.d.ts", "default": "./dist/commonjs/index.js" } diff --git a/scripts/repro.tsx b/scripts/repro.tsx new file mode 100644 index 000000000..278ab9e60 --- /dev/null +++ b/scripts/repro.tsx @@ -0,0 +1,34 @@ +// @ts-ignore +import React from 'react'; + +import { render } from '../packages/jsx-email/src/renderer/render.ts'; +import { Conditional, Raw } from '../packages/jsx-email/src/index.ts'; + +const fragment = ( + + + + + + + + + + ' + } + /> + + + + + + + + + +); + +const html = await render(fragment, { pretty: true }); +console.log(html);