core icon indicating copy to clipboard operation
core copied to clipboard

fix(runtime-core): ensure correct anchor el for deeper unresolved async components

Open linzhe141 opened this issue 2 weeks ago β€’ 5 comments

close #14173

This is a follow-up to this PR https://github.com/vuejs/core/pull/13560.

The following issue examples are just a sufficiently complex case to describe the current issues with patchKeyedChildren + async component wrapper, this maby a edge case.

this pr playground

issue playground

Summary by CodeRabbit

  • Bug Fixes

    • Improved placeholder resolution for nested/wrapped async components so Suspense handles complex component hierarchies and nested placeholders more reliably during rendering and updates.
  • Tests

    • Added coverage for nested async components rendered via v-for with additional wrapper layers, validating initial render and subsequent list and item updates.

✏️ Tip: You can customize this high-level summary in your review settings.

linzhe141 avatar Dec 08 '25 15:12 linzhe141

Walkthrough

Replaces a direct fallback to an anchor vnode placeholder with an internal resolver that recursively inspects component wrapper VNodes to locate async component placeholders; adds a test exercising Suspense + v-for with nested async wrappers and list updates.

Changes

Cohort / File(s) Summary
Runtime: async placeholder resolution
packages/runtime-core/src/renderer.ts
Added internal helper resolveAsyncComponentPlaceholder(anchorVnode: VNode) that returns anchorVnode.placeholder or recursively inspects wrapper component subTrees to locate a placeholder; replaced direct fallback (`anchorVNode.el
Tests: Suspense + async wrappers
packages/runtime-core/__tests__/components/Suspense.spec.ts
Added a new test that renders multiple asynchronously wrapped components inside <Suspense> with v-for and keys, then updates the list and item data to assert correct DOM order and reactive updates through nested wrappers.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review focus:
    • Correctness and termination of resolveAsyncComponentPlaceholder recursion (nested wrapper traversal, null handling).
    • Ensure all renderer anchor-resolution paths behave correctly for anchors, placeholders, and wrapped components.
    • Validate test determinism and coverage for the linked issue (#14173).

Possibly related PRs

  • vuejs/core#13456 β€” Modifies runtime-core renderer behavior around async component and Suspense handling; overlaps with placeholder resolution logic.
  • vuejs/core#13560 β€” Adjusts renderer handling of unresolved async component placeholders; touches similar anchor/placeholder code paths.
  • vuejs/core#13928 β€” Changes vnode.placeholder lifecycle/clearing for async placeholders; related to placeholder traversal and cleanup.

Suggested labels

scope: suspense

Poem

πŸ‡ I hopped through wrappers, quiet and light,
Searched for the placeholder hidden from sight.
Keys held their places, order stayed true,
A tiny rabbit fix β€” and rendering too. πŸ₯•

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
βœ… Passed checks (4 passed)
Check name Status Explanation
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check βœ… Passed The title accurately describes the core fix: ensuring correct anchor element resolution for unresolved async components nested deeper in component wrappers.
Linked Issues check βœ… Passed The code changes directly address the linked issue #14173 by fixing anchor element resolution for deeply wrapped async components in v-for loops with Suspense.
Out of Scope Changes check βœ… Passed All changes are tightly scoped to fixing the async component anchor element issue: internal helper function and test case with no unrelated modifications.
✨ Finishing touches
  • [ ] πŸ“ Generate docstrings
πŸ§ͺ Generate unit tests (beta)
  • [ ] Create PR with unit tests
  • [ ] Post copyable unit tests in a comment

πŸ“œ Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 13995cb88dda3dc659b582c24b53013c8d42fcfe and 82a13bb6faaa9f77a06b57e69e0934b9f620f333.

πŸ“’ Files selected for processing (1)
  • packages/runtime-core/__tests__/components/Suspense.spec.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/runtime-core/__tests__/components/Suspense.spec.ts (3)
packages/runtime-core/src/components/Suspense.ts (1)
  • Suspense (131-142)
packages/runtime-core/src/vnode.ts (1)
  • Fragment (63-68)
packages/runtime-test/src/serialize.ts (1)
  • serializeInner (22-33)
πŸ”‡ Additional comments (1)
packages/runtime-core/__tests__/components/Suspense.spec.ts (1)

2363-2455: New nested async + Suspense + v-for test accurately covers the #14173 regression

The new test constructs a realistic chain of async wrappers (CompAsyncWrapper, CompWrapper) around a Suspense-wrapped v-for of async components (CompAsyncSetup), then exercises:

  • Initial resolution of all async layers,
  • Replacing the outer keyed list (list update),
  • Mutating the inner items array (items update),

while asserting both ordering and content for each phase. The double await Promise.all(deps) pattern per phase matches the two async layers introduced by the local defineAsyncComponent helper and is consistent with similar nested-async tests earlier in this file. Overall, this is a focused, deterministic regression test that should robustly guard the fixed anchor-resolution behavior in patchKeyedChildren for this edge case.


Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot] avatar Dec 08 '25 15:12 coderabbitai[bot]

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 103 kB (+87 B) 39 kB (+32 B) 35.1 kB (+11 B)
vue.global.prod.js 161 kB (+87 B) 59 kB (+35 B) 52.5 kB (-42 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 47 kB (+87 B) 18.4 kB (+28 B) 16.8 kB (+25 B)
createApp 55.2 kB (+83 B) 21.4 kB (+27 B) 19.6 kB (+6 B)
createSSRApp 59.5 kB (+87 B) 23.2 kB (+29 B) 21.1 kB (+27 B)
defineCustomElement 60.8 kB (+87 B) 23.2 kB (+28 B) 21.1 kB (+27 B)
overall 69.5 kB (+87 B) 26.6 kB (+27 B) 24.3 kB (-23 B)

github-actions[bot] avatar Dec 08 '25 15:12 github-actions[bot]

Open in StackBlitz

@vue/compiler-core

pnpm add https://pkg.pr.new/@vue/compiler-core@14182
npm i https://pkg.pr.new/@vue/compiler-core@14182
yarn add https://pkg.pr.new/@vue/[email protected]
@vue/compiler-dom

pnpm add https://pkg.pr.new/@vue/compiler-dom@14182
npm i https://pkg.pr.new/@vue/compiler-dom@14182
yarn add https://pkg.pr.new/@vue/[email protected]
@vue/compiler-sfc

pnpm add https://pkg.pr.new/@vue/compiler-sfc@14182
npm i https://pkg.pr.new/@vue/compiler-sfc@14182
yarn add https://pkg.pr.new/@vue/[email protected]
@vue/compiler-ssr

pnpm add https://pkg.pr.new/@vue/compiler-ssr@14182
npm i https://pkg.pr.new/@vue/compiler-ssr@14182
yarn add https://pkg.pr.new/@vue/[email protected]
@vue/reactivity

pnpm add https://pkg.pr.new/@vue/reactivity@14182
npm i https://pkg.pr.new/@vue/reactivity@14182
yarn add https://pkg.pr.new/@vue/[email protected]
@vue/runtime-core

pnpm add https://pkg.pr.new/@vue/runtime-core@14182
npm i https://pkg.pr.new/@vue/runtime-core@14182
yarn add https://pkg.pr.new/@vue/[email protected]
@vue/runtime-dom

pnpm add https://pkg.pr.new/@vue/runtime-dom@14182
npm i https://pkg.pr.new/@vue/runtime-dom@14182
yarn add https://pkg.pr.new/@vue/[email protected]
@vue/server-renderer

pnpm add https://pkg.pr.new/@vue/server-renderer@14182
npm i https://pkg.pr.new/@vue/server-renderer@14182
yarn add https://pkg.pr.new/@vue/[email protected]
@vue/shared

pnpm add https://pkg.pr.new/@vue/shared@14182
npm i https://pkg.pr.new/@vue/shared@14182
yarn add https://pkg.pr.new/@vue/[email protected]
vue

pnpm add https://pkg.pr.new/vue@14182
npm i https://pkg.pr.new/vue@14182
yarn add https://pkg.pr.new/[email protected]
@vue/compat

pnpm add https://pkg.pr.new/@vue/compat@14182
npm i https://pkg.pr.new/@vue/compat@14182
yarn add https://pkg.pr.new/@vue/[email protected]

commit: 82a13bb

pkg-pr-new[bot] avatar Dec 08 '25 15:12 pkg-pr-new[bot]

/ecosystem-ci run

edison1105 avatar Dec 09 '25 03:12 edison1105

πŸ“ Ran ecosystem CI: Open

suite result latest scheduled
nuxt :white_check_mark: success :white_check_mark: success
language-tools :x: failure :white_check_mark: success
pinia :white_check_mark: success :white_check_mark: success
router :x: failure :x: failure
vant :white_check_mark: success :white_check_mark: success
primevue :white_check_mark: success :white_check_mark: success
test-utils :white_check_mark: success :white_check_mark: success
vite-plugin-vue :white_check_mark: success :white_check_mark: success
quasar :white_check_mark: success :white_check_mark: success
radix-vue :white_check_mark: success :white_check_mark: success
vitepress :white_check_mark: success :white_check_mark: success
vue-i18n :x: failure :x: failure
vueuse :white_check_mark: success :white_check_mark: success
vue-macros :x: failure :x: failure
vue-simple-compiler :white_check_mark: success :white_check_mark: success
vuetify :x: failure :x: failure

vue-bot avatar Dec 09 '25 03:12 vue-bot