Skip to content

Commit da7ad5e

Browse files
authoredNov 15, 2024··
fix(ssr): avoid updating subtree of async component if it is resolved (#12363)
close #12362
1 parent 1f75d4e commit da7ad5e

File tree

2 files changed

+83
-2
lines changed

2 files changed

+83
-2
lines changed
 

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

+78
Original file line numberDiff line numberDiff line change
@@ -1324,6 +1324,84 @@ describe('SSR hydration', () => {
13241324
resolve({})
13251325
})
13261326

1327+
//#12362
1328+
test('nested async wrapper', async () => {
1329+
const Toggle = defineAsyncComponent(
1330+
() =>
1331+
new Promise(r => {
1332+
r(
1333+
defineComponent({
1334+
setup(_, { slots }) {
1335+
const show = ref(false)
1336+
onMounted(() => {
1337+
nextTick(() => {
1338+
show.value = true
1339+
})
1340+
})
1341+
return () =>
1342+
withDirectives(
1343+
h('div', null, [renderSlot(slots, 'default')]),
1344+
[[vShow, show.value]],
1345+
)
1346+
},
1347+
}) as any,
1348+
)
1349+
}),
1350+
)
1351+
1352+
const Wrapper = defineAsyncComponent(() => {
1353+
return new Promise(r => {
1354+
r(
1355+
defineComponent({
1356+
render(this: any) {
1357+
return renderSlot(this.$slots, 'default')
1358+
},
1359+
}) as any,
1360+
)
1361+
})
1362+
})
1363+
1364+
const count = ref(0)
1365+
const fn = vi.fn()
1366+
const Child = {
1367+
setup() {
1368+
onMounted(() => {
1369+
fn()
1370+
count.value++
1371+
})
1372+
return () => h('div', count.value)
1373+
},
1374+
}
1375+
1376+
const App = {
1377+
render() {
1378+
return h(Toggle, null, {
1379+
default: () =>
1380+
h(Wrapper, null, {
1381+
default: () =>
1382+
h(Wrapper, null, {
1383+
default: () => h(Child),
1384+
}),
1385+
}),
1386+
})
1387+
},
1388+
}
1389+
1390+
const root = document.createElement('div')
1391+
root.innerHTML = await renderToString(h(App))
1392+
expect(root.innerHTML).toMatchInlineSnapshot(
1393+
`"<div style="display:none;"><!--[--><!--[--><!--[--><div>0</div><!--]--><!--]--><!--]--></div>"`,
1394+
)
1395+
1396+
createSSRApp(App).mount(root)
1397+
await nextTick()
1398+
await nextTick()
1399+
expect(root.innerHTML).toMatchInlineSnapshot(
1400+
`"<div style=""><!--[--><!--[--><!--[--><div>1</div><!--]--><!--]--><!--]--></div>"`,
1401+
)
1402+
expect(fn).toBeCalledTimes(1)
1403+
})
1404+
13271405
test('unmount async wrapper before load (fragment)', async () => {
13281406
let resolve: any
13291407
const AsyncComp = defineAsyncComponent(

‎packages/runtime-core/src/hydration.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
normalizeVNode,
1212
} from './vnode'
1313
import { flushPostFlushCbs } from './scheduler'
14-
import type { ComponentInternalInstance } from './component'
14+
import type { ComponentInternalInstance, ComponentOptions } from './component'
1515
import { invokeDirectiveHook } from './directives'
1616
import { warn } from './warning'
1717
import {
@@ -308,7 +308,10 @@ export function createHydrationFunctions(
308308
// if component is async, it may get moved / unmounted before its
309309
// inner component is loaded, so we need to give it a placeholder
310310
// vnode that matches its adopted DOM.
311-
if (isAsyncWrapper(vnode)) {
311+
if (
312+
isAsyncWrapper(vnode) &&
313+
!(vnode.type as ComponentOptions).__asyncResolved
314+
) {
312315
let subTree
313316
if (isFragmentStart) {
314317
subTree = createVNode(Fragment)

0 commit comments

Comments
 (0)
Please sign in to comment.