fix(runtime-core): ensure correct anchor el for deeper unresolved async components
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.
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.
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
resolveAsyncComponentPlaceholderrecursion (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).
- Correctness and termination of
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 regressionThe 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 (
listupdate),- Mutating the inner items array (
itemsupdate),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 localdefineAsyncComponenthelper 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 inpatchKeyedChildrenfor this edge case.
Comment @coderabbitai help to get the list of available commands and usage tips.
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) |
@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
/ecosystem-ci run
π 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 |