Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions src/render/createRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,25 @@ export async function createRenderer(
html = html.replace('</body>', `${bodyTags}\n</body>`)
}

/**
* Strip Vue SSR fragment markers + teleport anchor comments. These
* are rendering hygiene, not transformer concerns — must run
* regardless of `useTransformers` state. Fragment markers contain
* `-->`, which would prematurely terminate MSO conditional
* comments downstream — including in the DOM round-trip below,
* whose parser would otherwise close `<!--[if mso]>` at a
* marker's `-->` and mangle the conditional on re-serialize.
*/
const stripSsrMarkers = (str: string) => str
.replaceAll('<!--[-->', '')
.replaceAll('<!--]-->', '')
.replaceAll('<!--teleport start anchor-->', '')
.replaceAll('<!--teleport anchor-->', '')
.replaceAll('<!--teleport start-->', '')
.replaceAll('<!--teleport end-->', '')

html = stripSsrMarkers(html)

// Inject SSR teleport content into their target elements
const hasTeleports = ssrContext.teleports && Object.keys(ssrContext.teleports).length > 0
const hasFonts = (renderContext.fonts?.length ?? 0) > 0
Expand All @@ -525,7 +544,7 @@ export async function createRenderer(

const prepend = rawTarget.endsWith(':start')
const target = prepend ? rawTarget.slice(0, -6) : rawTarget
const targetChildren = parseDom(content)
const targetChildren = parseDom(stripSsrMarkers(content))

walk(dom, (node) => {
const el = node as import('domhandler').Element
Expand Down Expand Up @@ -566,21 +585,6 @@ export async function createRenderer(
html = html.replace(/<body([^>]*)>/, `<body$1>${previewHtml}`)
}

/**
* Strip Vue SSR fragment markers + teleport anchor comments. These
* are rendering hygiene, not transformer concerns — must run
* regardless of `useTransformers` state. Fragment markers contain
* `-->`, which would prematurely terminate MSO conditional
* comments downstream.
*/
html = html
.replaceAll('<!--[-->', '')
.replaceAll('<!--]-->', '')
.replaceAll('<!--teleport start anchor-->', '')
.replaceAll('<!--teleport anchor-->', '')
.replaceAll('<!--teleport start-->', '')
.replaceAll('<!--teleport end-->', '')

return {
html,
doctype: renderContext.doctype,
Expand Down
20 changes: 20 additions & 0 deletions src/tests/render/createRenderer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,26 @@ describe('createRenderer', () => {
expect(result.html).toContain('fonts.googleapis.com')
})

it('keeps MSO conditionals intact when Preheader triggers the teleport DOM round-trip', async () => {
const config = await resolveConfig({ root: tempDir })
const result = await render(
`<template>
<Html>
<Head />
<Body>
<Preheader>Preview text</Preheader>
<Outlook><img src="https://example.com/image-a.gif"></Outlook>
<NotOutlook><img src="https://example.com/image-b.gif"></NotOutlook>
</Body>
</Html>
</template>`,
config,
)
expect(result.html).toContain('<!--[if mso]><img src="https://example.com/image-a.gif"><![endif]-->')
expect(result.html).toContain('<!--[if !mso]><!--><img src="https://example.com/image-b.gif"')
expect(result.html).not.toContain('---->')
})

it('wraps a markdown code fence in a table with shiki highlighting', async () => {
writeFileSync(join(tempDir, 'page.md'), '# Title\n\n```js\nconst x = 1\n```\n')
const result = await render(join(tempDir, 'page.md'), { useTransformers: false })
Expand Down
Loading