diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts
index 0dca0ba9ab4..8aba1f2c1b2 100644
--- a/packages/compiler-core/src/transforms/vFor.ts
+++ b/packages/compiler-core/src/transforms/vFor.ts
@@ -42,6 +42,7 @@ import {
import {
FRAGMENT,
IS_MEMO_SAME,
+ KEEP_ALIVE,
OPEN_BLOCK,
RENDER_LIST,
} from '../runtimeHelpers'
@@ -208,7 +209,9 @@ export const transformFor: NodeTransform = createStructuralDirectiveTransform(
)
}
}
- childBlock.isBlock = !isStableFragment
+ // track KeepAlive child as block
+ childBlock.isBlock =
+ childBlock.tag === KEEP_ALIVE || !isStableFragment
if (childBlock.isBlock) {
helper(OPEN_BLOCK)
helper(getVNodeBlockHelper(context.inSSR, childBlock.isComponent))
diff --git a/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts b/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
index 1d8bb84205b..f6fd16692c4 100644
--- a/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
+++ b/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
@@ -1,10 +1,12 @@
import {
Fragment,
type FunctionalComponent,
+ KeepAlive,
type SetupContext,
Teleport,
type TestElement,
type VNode,
+ computed,
createApp,
createBlock,
createCommentVNode,
@@ -1402,4 +1404,54 @@ describe('renderer: optimized mode', () => {
expect(inner(root)).toBe('')
expect(beforeUnmountSpy).toHaveBeenCalledTimes(1)
})
+
+ //#12914
+ test('unmount KeepAlive children when wrapped in v-for with stable fragment', async () => {
+ const CompA = {
+ setup() {
+ return () => h('span', 'CompA')
+ },
+ }
+ const CompB = {
+ setup() {
+ return () => h('span', 'CompB')
+ },
+ }
+
+ const toggle = ref(true)
+ const view = computed(() => {
+ return toggle.value ? CompA : CompB
+ })
+
+ const app = createApp({
+ render() {
+ return (
+ openBlock(),
+ createElementBlock(
+ Fragment,
+ null,
+ renderList(1, () => {
+ return (
+ openBlock(),
+ createBlock(
+ KeepAlive,
+ { include: [] },
+ [(openBlock(), createBlock(view.value))],
+ 1024 /* DYNAMIC_SLOTS */,
+ )
+ )
+ }),
+ 64 /* STABLE_FRAGMENT */,
+ )
+ )
+ },
+ })
+
+ app.mount(root)
+ expect(inner(root)).toBe('CompA')
+
+ toggle.value = false
+ await nextTick()
+ expect(inner(root)).toBe('CompB')
+ })
})