Skip to content

fix(KeepAlive): unmount pruned current component when its vnode was cloned on mount - #15711

Merged
edison1105 merged 1 commit into
vuejs:mainfrom
lazerg:fix/issue-15710-keepalive-prune-current
Oct 12, 2026
Merged

edison1105 merged 1 commit into
vuejs:mainfrom
lazerg:fix/issue-15710-keepalive-prune-current

Conversation

@lazerg

@lazerg lazerg commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

close #15710

When KeepAlive re-renders and the child is found in the cache, the returned vnode gets el from the cached one, so normalizeVNode (cloneIfMounted) in renderComponentRoot clones it. That clone is what gets mounted and cached, so current and the cached vnode end up being different objects. When max then prunes the entry of the component on screen, pruneCacheEntry only resets the flag on current. The mounted vnode keeps COMPONENT_SHOULD_KEEP_ALIVE, so it gets deactivated instead of unmounted.

The fix resets the flag on the cached vnode as well. Returning the already-mounted parent vnode, as in the issue, is one way to hit this; changing a prop on the child before switching views does it too.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed a KeepAlive caching issue where a previously mounted component could be unmounted incorrectly when switching components with a cache limit of one.

@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 4c051a2e-dcd8-4403-915d-300d89c38f3a

📥 Commits

Reviewing files that changed from the base of the PR and between 4ab865a and 52d3250.

📒 Files selected for processing (2)
  • packages/runtime-core/__tests__/components/KeepAlive.spec.ts
  • packages/runtime-core/src/components/KeepAlive.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

When pruneCacheEntry prunes the current entry, it now resets the cached vnode’s KeepAlive flags. A regression test covers reusing a mounted vnode before switching views with max: 1.

Changes

KeepAlive pruning

Layer / File(s) Summary
Reset flags and test current-entry pruning
packages/runtime-core/src/components/KeepAlive.ts, packages/runtime-core/__tests__/components/KeepAlive.spec.ts
pruneCacheEntry resets the cached vnode’s KeepAlive flags when current exists. The regression test checks that the previous component is unmounted once without deactivation.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~8 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 52d32

The change addresses the reported unmount behavior, with no actionable merge risk identified beyond normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 52d32

The change is confined to component cache teardown and corrects how a reused component is unmounted. No new access path or external dependency is evident, but validation of unusual scheduling cases is limited.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated effect is limited to components affected by KeepAlive current-entry pruning. The evidence does not establish a new cross-service, credential, or data-store exposure.

Trust Boundaries and Controls

  • inferred — KeepAlive’s public component entrypoint still reaches the existing internal pruning and renderer paths. The changed flag reset does not itself establish a new trust transition or bypass of a security control.

Resilience and Maintainability Implications

  • inferred — Resetting flags before deleting the cache entry preserves the replacement path’s ordering; normal unmount supplies cleanup that deactivation would skip. The supplied regression test covers the reported clone mismatch, not every interrupted or concurrent scheduling case.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Issue #15710 requires a pruned current component to unmount when KeepAlive reuses an already-mounted vnode, including the max: 1 case. pruneCacheEntry now resets the KeepAlive flags on both `current…
Out of Scope Changes check ✅ Passed The reviewed changes modify only KeepAlive.pruneCacheEntry and its regression test. Both changes directly support issue #15710. No unrelated implementation, API, or test changes are present in the s…
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the KeepAlive fix and the specific condition involving a cloned vnode during mount. It matches the main change and issue objective.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@pkg-pr-new

pkg-pr-new Bot commented Sep 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

@vue/compiler-core

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

@vue/compiler-dom

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

@vue/compiler-sfc

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

@vue/compiler-ssr

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

@vue/reactivity

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

@vue/runtime-core

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

@vue/runtime-dom

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

@vue/server-renderer

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

@vue/shared

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

vue

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

@vue/compat

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

commit: 52d3250

@github-actions

Copy link
Copy Markdown

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 109 kB (+11 B) 41.3 kB (+4 B) 37 kB (+38 B)
vue.global.prod.js 168 kB (+11 B) 61.5 kB (+6 B) 54.6 kB (-10 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 50.8 kB 19.6 kB 18 kB
createApp 58.9 kB 22.7 kB 20.8 kB
createSSRApp 63.7 kB 24.7 kB 22.5 kB
defineCustomElement 65.2 kB 24.6 kB 22.5 kB
overall 73.5 kB (+11 B) 28 kB (+6 B) 25.5 kB (+42 B)

@edison1105 edison1105 added ready to merge The PR is ready to be merged. scope: keep-alive labels Sep 29, 2026
@edison1105

Copy link
Copy Markdown
Member

/ecosystem-ci run

@vue-bot

vue-bot commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

📝 Ran ecosystem CI: Open

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready to merge The PR is ready to be merged. scope: keep-alive

Projects

None yet

Development

Successfully merging this pull request may close these issues.

KeepAlive with max deactivates instead of unmounting the pruned current component when the render returned an already-mounted vnode (instance leaks)

3 participants