diff --git a/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts b/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts
index 829421ca2..754d83ac6 100644
--- a/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts
+++ b/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts
@@ -5,7 +5,9 @@ import {
onDeactivated,
onMounted,
onUnmounted,
+ reactive,
ref,
+ shallowRef,
} from 'vue'
import type { LooseRawProps, VaporComponent } from '../../src/component'
import { makeRender } from '../_utils'
@@ -1037,4 +1039,151 @@ describe('VaporKeepAlive', () => {
expect(html()).toBe(`
1
`)
})
})
+
+ test.todo('should work with async component', async () => {})
+
+ test('handle error in async onActivated', async () => {
+ const err = new Error('foo')
+ const handler = vi.fn()
+ const Child = defineVaporComponent({
+ setup() {
+ onActivated(async () => {
+ throw err
+ })
+
+ return template(` createComponent(Child),
+ })
+ },
+ }).create()
+
+ app.config.errorHandler = handler
+ app.mount(document.createElement('div'))
+
+ await nextTick()
+ expect(handler).toHaveBeenCalledTimes(1)
+ })
+
+ test('should avoid unmount later included components', async () => {
+ const unmountedA = vi.fn()
+ const mountedA = vi.fn()
+ const activatedA = vi.fn()
+ const deactivatedA = vi.fn()
+ const unmountedB = vi.fn()
+ const mountedB = vi.fn()
+
+ const A = defineVaporComponent({
+ name: 'A',
+ setup() {
+ onMounted(mountedA)
+ onUnmounted(unmountedA)
+ onActivated(activatedA)
+ onDeactivated(deactivatedA)
+ return template(`A
`)()
+ },
+ })
+
+ const B = defineVaporComponent({
+ name: 'B',
+ setup() {
+ onMounted(mountedB)
+ onUnmounted(unmountedB)
+ return template(`B
`)()
+ },
+ })
+
+ const include = reactive([])
+ const current = shallowRef(A)
+ const { html } = define({
+ setup() {
+ return createComponent(
+ VaporKeepAlive,
+ { include: () => include },
+ {
+ default: () => createDynamicComponent(() => current.value),
+ },
+ )
+ },
+ }).render()
+
+ expect(html()).toBe(`A
`)
+ expect(mountedA).toHaveBeenCalledTimes(1)
+ expect(unmountedA).toHaveBeenCalledTimes(0)
+ expect(activatedA).toHaveBeenCalledTimes(0)
+ expect(deactivatedA).toHaveBeenCalledTimes(0)
+ expect(mountedB).toHaveBeenCalledTimes(0)
+ expect(unmountedB).toHaveBeenCalledTimes(0)
+
+ include.push('A') // cache A
+ await nextTick()
+ current.value = B // toggle to B
+ await nextTick()
+ expect(html()).toBe(`B
`)
+ expect(mountedA).toHaveBeenCalledTimes(1)
+ expect(unmountedA).toHaveBeenCalledTimes(0)
+ expect(activatedA).toHaveBeenCalledTimes(0)
+ expect(deactivatedA).toHaveBeenCalledTimes(1)
+ expect(mountedB).toHaveBeenCalledTimes(1)
+ expect(unmountedB).toHaveBeenCalledTimes(0)
+ })
+
+ test('remove component from include then switching child', async () => {
+ const About = defineVaporComponent({
+ name: 'About',
+ setup() {
+ return template(`About
`)()
+ },
+ })
+ const mountedHome = vi.fn()
+ const unmountedHome = vi.fn()
+ const activatedHome = vi.fn()
+ const deactivatedHome = vi.fn()
+
+ const Home = defineVaporComponent({
+ name: 'Home',
+ setup() {
+ onMounted(mountedHome)
+ onUnmounted(unmountedHome)
+ onDeactivated(deactivatedHome)
+ onActivated(activatedHome)
+ return template(`Home
`)()
+ },
+ })
+
+ const activeViewName = ref('Home')
+ const cacheList = reactive(['Home'])
+
+ define({
+ setup() {
+ return createComponent(
+ VaporKeepAlive,
+ { include: () => cacheList },
+ {
+ default: () => {
+ return createIf(
+ () => activeViewName.value === 'Home',
+ () => createComponent(Home),
+ () => createComponent(About),
+ )
+ },
+ },
+ )
+ },
+ }).render()
+
+ expect(mountedHome).toHaveBeenCalledTimes(1)
+ expect(activatedHome).toHaveBeenCalledTimes(1)
+ cacheList.splice(0, 1)
+ await nextTick()
+ activeViewName.value = 'About'
+ await nextTick()
+ expect(deactivatedHome).toHaveBeenCalledTimes(0)
+ expect(unmountedHome).toHaveBeenCalledTimes(1)
+ })
})
diff --git a/packages/runtime-vapor/src/components/KeepAlive.ts b/packages/runtime-vapor/src/components/KeepAlive.ts
index ec7cce0be..127f0c94f 100644
--- a/packages/runtime-vapor/src/components/KeepAlive.ts
+++ b/packages/runtime-vapor/src/components/KeepAlive.ts
@@ -12,7 +12,13 @@ import {
warn,
watch,
} from '@vue/runtime-dom'
-import { type Block, insert, isFragment, isValidBlock } from '../block'
+import {
+ type Block,
+ DynamicFragment,
+ insert,
+ isFragment,
+ isValidBlock,
+} from '../block'
import {
type ObjectVaporComponent,
type VaporComponent,
@@ -153,7 +159,7 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
}
}
- const children = slots.default()
+ let children = slots.default()
if (isArray(children) && children.length > 1) {
if (__DEV__) {
warn(`KeepAlive should contain exactly one component child.`)
@@ -161,6 +167,13 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
return children
}
+ // wrap children in dynamic fragment
+ if (!isFragment(children)) {
+ const frag = new DynamicFragment()
+ frag.update(() => children)
+ children = frag
+ }
+
function pruneCache(filter: (name: string) => boolean) {
cache.forEach((instance, key) => {
const name = getComponentName(instance.type)
@@ -197,16 +210,6 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
},
})
-export const VaporKeepAlive = VaporKeepAliveImpl as any as {
- __isKeepAlive: true
- new (): {
- $props: KeepAliveProps
- $slots: {
- default(): Block
- }
- }
-}
-
function getInnerBlock(block: Block): VaporComponentInstance | undefined {
if (isVaporComponent(block)) {
return block
diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts
index 6cc06a72e..f656520e3 100644
--- a/packages/runtime-vapor/src/index.ts
+++ b/packages/runtime-vapor/src/index.ts
@@ -3,7 +3,7 @@ export { createVaporApp, createVaporSSRApp } from './apiCreateApp'
export { defineVaporComponent } from './apiDefineComponent'
export { vaporInteropPlugin } from './vdomInterop'
export type { VaporDirective } from './directives/custom'
-export { VaporKeepAlive } from './components/KeepAlive'
+export { VaporKeepAliveImpl as VaporKeepAlive } from './components/KeepAlive'
// compiler-use only
export { insert, prepend, remove, isFragment, VaporFragment } from './block'