Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d9e0a5c

Browse files
authored
fix(server-renderer): render unresolved tag fallback as element (#14794)
1 parent 1de6fc4 commit d9e0a5c

5 files changed

Lines changed: 102 additions & 4 deletions

File tree

‎packages/runtime-core/__tests__/hydration.spec.ts‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,38 @@ describe('SSR hydration', () => {
809809
expect(teleportContainer2.innerHTML).toBe('<span>Teleported</span>')
810810
})
811811

812+
test('hydrates unresolved tag fallback rendered as plain element', async () => {
813+
const msg = ref('foo')
814+
const App = {
815+
setup() {
816+
return { msg }
817+
},
818+
template: `
819+
<center><span>{{ msg }}</span></center>
820+
<span>after</span>
821+
`,
822+
}
823+
824+
const container = document.createElement('div')
825+
container.innerHTML = await renderToString(h(App))
826+
expect(container.innerHTML).toBe(
827+
'<!--[--><center><span>foo</span></center><span>after</span><!--]-->',
828+
)
829+
830+
createSSRApp(App).mount(container)
831+
expect(container.innerHTML).toBe(
832+
'<!--[--><center><span>foo</span></center><span>after</span><!--]-->',
833+
)
834+
835+
msg.value = 'bar'
836+
await nextTick()
837+
expect(container.innerHTML).toBe(
838+
'<!--[--><center><span>bar</span></center><span>after</span><!--]-->',
839+
)
840+
expect(`Failed to resolve component: center`).toHaveBeenWarned()
841+
expect(`Hydration node mismatch`).not.toHaveBeenWarned()
842+
})
843+
812844
// compile SSR + client render fn from the same template & hydrate
813845
test('full compiler integration', async () => {
814846
const mounted: string[] = []

‎packages/runtime-vapor/__tests__/hydration.spec.ts‎

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ async function testHydration(
143143
}
144144

145145
app.mount(container)
146-
return { data, container }
146+
return { data, container, html }
147147
}
148148

149149
const triggerEvent = (type: string, el: Element) => {
@@ -331,6 +331,41 @@ describe('Vapor Mode hydration', () => {
331331
expect(`mismatch in <div>`).not.toHaveBeenWarned()
332332
})
333333

334+
test('plain element fallback hydrates unresolved lowercase tag', async () => {
335+
const code = `
336+
<template>
337+
<center><span>{{ data }}</span></center>
338+
<span>after</span>
339+
</template>
340+
`
341+
const { container, data, html } = await testHydration(code)
342+
expect(formatHtml(html)).toMatchInlineSnapshot(
343+
`
344+
"
345+
<!--[--><center><span>foo</span></center><span>after</span><!--]-->
346+
"
347+
`,
348+
)
349+
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
350+
`
351+
"
352+
<!--[--><center><span>foo</span></center><span>after</span><!--]-->
353+
"
354+
`,
355+
)
356+
data.value = 'bar'
357+
await nextTick()
358+
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
359+
`
360+
"
361+
<!--[--><center><span>bar</span></center><span>after</span><!--]-->
362+
"
363+
`,
364+
)
365+
expect(`Failed to resolve component: center`).toHaveBeenWarned()
366+
expect(`Hydration node mismatch`).not.toHaveBeenWarned()
367+
})
368+
334369
test('element with binding and text children', async () => {
335370
const { container, data } = await testHydration(`
336371
<template><div :class="data">{{ data }}</div></template>

‎packages/runtime-vapor/src/component.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -967,7 +967,7 @@ export function createPlainElement(
967967
renderEffect(() => frag.update(getSlot(rawSlots as RawSlots, 'default')))
968968
if (!isHydrating) insert(frag, el)
969969
} else {
970-
let slot = getSlot(rawSlots as RawSlots, 'default')
970+
const slot = getSlot(rawSlots as RawSlots, 'default')
971971
if (slot) {
972972
const block = slot()
973973
if (!isHydrating) insert(block, el)

‎packages/server-renderer/__tests__/render.spec.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,17 @@ function testRender(type: string, render: typeof renderToString) {
232232
).toBe(`<div>parent<div>hello</div></div>`)
233233
})
234234

235+
test('renders unresolved tag fallback as plain element', async () => {
236+
const html = await render(
237+
createApp({
238+
template: `<center><span>foo</span></center>`,
239+
}),
240+
)
241+
242+
expect(html).toBe(`<center><span>foo</span></center>`)
243+
expect(`Failed to resolve component: center`).toHaveBeenWarned()
244+
})
245+
235246
test('nested template components', async () => {
236247
const Child = {
237248
props: ['msg'],

‎packages/server-renderer/src/helpers/ssrRenderComponent.ts‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,36 @@ import {
44
type Slots,
55
createVNode,
66
} from 'vue'
7-
import { type Props, type SSRBuffer, renderComponentVNode } from '../render'
7+
import { isString } from '@vue/shared'
8+
import {
9+
type Props,
10+
type SSRBuffer,
11+
createBuffer,
12+
renderComponentVNode,
13+
renderVNode,
14+
} from '../render'
815
import type { SSRSlots } from './ssrRenderSlot'
916

1017
export function ssrRenderComponent(
11-
comp: Component,
18+
comp: Component | string,
1219
props: Props | null = null,
1320
children: Slots | SSRSlots | null = null,
1421
parentComponent: ComponentInternalInstance | null = null,
1522
slotScopeId?: string,
1623
): SSRBuffer | Promise<SSRBuffer> {
24+
if (isString(comp)) {
25+
// resolveComponent() can fall back to the original tag string; render it
26+
// through the element path so SSR matches the client plain-element fallback.
27+
const { getBuffer, push } = createBuffer()
28+
renderVNode(
29+
push,
30+
createVNode(comp, props, children),
31+
parentComponent as ComponentInternalInstance,
32+
slotScopeId,
33+
)
34+
return getBuffer()
35+
}
36+
1737
return renderComponentVNode(
1838
createVNode(comp, props, children),
1939
parentComponent,

0 commit comments

Comments
 (0)