From: linzhe <40790268+linzhe141@users.noreply.github.com>
Date: Wed, 23 Jul 2025 00:42:10 +0000 (+0800)
Subject: fix(runtime-core): ensure correct anchor el for unresolved async components (#13560)
X-Git-Tag: v3.5.18~3
X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7f2994393dcdb82cacbf62e02b5ba5565f32588b;p=thirdparty%2Fvuejs%2Fcore.git
fix(runtime-core): ensure correct anchor el for unresolved async components (#13560)
close #13559
---
diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts
index 65e801de27..563c91a179 100644
--- a/packages/runtime-core/__tests__/components/Suspense.spec.ts
+++ b/packages/runtime-core/__tests__/components/Suspense.spec.ts
@@ -2230,5 +2230,57 @@ describe('Suspense', () => {
fallback: [h('div'), h('div')],
})
})
+
+ // #13559
+ test('renders multiple async components in Suspense with v-for and updates on items change', async () => {
+ const CompAsyncSetup = defineAsyncComponent({
+ props: ['item'],
+ render(ctx: any) {
+ return h('div', ctx.item.name)
+ },
+ })
+
+ const items = ref([
+ { id: 1, name: '111' },
+ { id: 2, name: '222' },
+ { id: 3, name: '333' },
+ ])
+
+ const Comp = {
+ setup() {
+ return () =>
+ h(Suspense, null, {
+ default: () =>
+ h(
+ Fragment,
+ null,
+ items.value.map(item =>
+ h(CompAsyncSetup, { item, key: item.id }),
+ ),
+ ),
+ })
+ },
+ }
+
+ const root = nodeOps.createElement('div')
+ render(h(Comp), root)
+ await nextTick()
+ await Promise.all(deps)
+
+ expect(serializeInner(root)).toBe(
+ `
111
222
333
`,
+ )
+
+ items.value = [
+ { id: 4, name: '444' },
+ { id: 5, name: '555' },
+ { id: 6, name: '666' },
+ ]
+ await nextTick()
+ await Promise.all(deps)
+ expect(serializeInner(root)).toBe(
+ `444
555
666
`,
+ )
+ })
})
})
diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts
index a57be791a4..f046e93ad8 100644
--- a/packages/runtime-core/src/renderer.ts
+++ b/packages/runtime-core/src/renderer.ts
@@ -1226,6 +1226,7 @@ function baseCreateRenderer(
if (!initialVNode.el) {
const placeholder = (instance.subTree = createVNode(Comment))
processCommentNode(null, placeholder, container!, anchor)
+ initialVNode.placeholder = placeholder.el
}
} else {
setupRenderEffect(
@@ -1979,8 +1980,12 @@ function baseCreateRenderer(
for (i = toBePatched - 1; i >= 0; i--) {
const nextIndex = s2 + i
const nextChild = c2[nextIndex] as VNode
+ const anchorVNode = c2[nextIndex + 1] as VNode
const anchor =
- nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor
+ nextIndex + 1 < l2
+ ? // #13559, fallback to el placeholder for unresolved async component
+ anchorVNode.el || anchorVNode.placeholder
+ : parentAnchor
if (newIndexToOldIndexMap[i] === 0) {
// mount new
patch(
diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts
index a8c5340cd1..cd1ef948d7 100644
--- a/packages/runtime-core/src/vnode.ts
+++ b/packages/runtime-core/src/vnode.ts
@@ -196,6 +196,7 @@ export interface VNode<
// DOM
el: HostNode | null
+ placeholder: HostNode | null // async component el placeholder
anchor: HostNode | null // fragment anchor
target: HostElement | null // teleport target
targetStart: HostNode | null // teleport target start anchor
@@ -711,6 +712,8 @@ export function cloneVNode(
suspense: vnode.suspense,
ssContent: vnode.ssContent && cloneVNode(vnode.ssContent),
ssFallback: vnode.ssFallback && cloneVNode(vnode.ssFallback),
+ placeholder: vnode.placeholder,
+
el: vnode.el,
anchor: vnode.anchor,
ctx: vnode.ctx,