Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 100 additions & 3 deletions packages/runtime-vapor/__tests__/apiDefineAsyncComponent.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { nextTick, ref } from '@vue/runtime-dom'
import { nextTick, onActivated, ref } from '@vue/runtime-dom'
import { type VaporComponent, createComponent } from '../src/component'
import { defineVaporAsyncComponent } from '../src/apiDefineAsyncComponent'
import { makeRender } from './_utils'
import {
VaporKeepAlive,
createIf,
createTemplateRefSetter,
defineVaporComponent,
renderEffect,
template,
} from '@vue/runtime-vapor'
Expand Down Expand Up @@ -758,7 +760,102 @@ describe('api: defineAsyncComponent', () => {

test.todo('suspense with error handling', async () => {})

test.todo('with KeepAlive', async () => {})
test('with KeepAlive', async () => {
const spy = vi.fn()
let resolve: (comp: VaporComponent) => void

const Foo = defineVaporAsyncComponent(
() =>
new Promise(r => {
resolve = r as any
}),
)

const Bar = defineVaporAsyncComponent(() =>
Promise.resolve(
defineVaporComponent({
setup() {
return template('Bar')()
},
}),
),
)

const toggle = ref(true)
const { html } = define({
setup() {
return createComponent(VaporKeepAlive, null, {
default: () =>
createIf(
() => toggle.value,
() => createComponent(Foo),
() => createComponent(Bar),
),
})
},
}).render()
expect(html()).toBe('<!--async component--><!--if-->')

await nextTick()
resolve!(
defineVaporComponent({
setup() {
onActivated(() => {
spy()
})
return template('Foo')()
},
}),
)

await timeout()
expect(html()).toBe('Foo<!--async component--><!--if-->')
expect(spy).toBeCalledTimes(1)

test.todo('with KeepAlive + include', async () => {})
toggle.value = false
await timeout()
expect(html()).toBe('Bar<!--async component--><!--if-->')
})

test('with KeepAlive + include', async () => {
const spy = vi.fn()
let resolve: (comp: VaporComponent) => void

const Foo = defineVaporAsyncComponent(
() =>
new Promise(r => {
resolve = r as any
}),
)

const { html } = define({
setup() {
return createComponent(
VaporKeepAlive,
{ include: () => 'Foo' },
{
default: () => createComponent(Foo),
},
)
},
}).render()
expect(html()).toBe('<!--async component-->')

await nextTick()
resolve!(
defineVaporComponent({
name: 'Foo',
setup() {
onActivated(() => {
spy()
})
return template('Foo')()
},
}),
)

await timeout()
expect(html()).toBe('Foo<!--async component-->')
expect(spy).toBeCalledTimes(1)
})
})
112 changes: 110 additions & 2 deletions packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
createIf,
createTemplateRefSetter,
createVaporApp,
defineVaporAsyncComponent,
defineVaporComponent,
renderEffect,
setText,
Expand All @@ -30,6 +31,7 @@ import {
} from '../../src'

const define = makeRender()
const timeout = (n: number = 0) => new Promise(r => setTimeout(r, n))

describe('VaporKeepAlive', () => {
let one: VaporComponent
Expand Down Expand Up @@ -1045,7 +1047,81 @@ describe('VaporKeepAlive', () => {
})
})

test.todo('should work with async component', async () => {})
test('should work with async component', async () => {
let resolve: (comp: VaporComponent) => void
const AsyncComp = defineVaporAsyncComponent(
() =>
new Promise(r => {
resolve = r as any
}),
)

const toggle = ref(true)
const instanceRef = ref<any>(null)
const { html } = define({
setup() {
const setRef = createTemplateRefSetter()
return createComponent(
VaporKeepAlive,
{ include: () => 'Foo' },
{
default: () => {
return createIf(
() => toggle.value,
() => {
const n0 = createComponent(AsyncComp)
setRef(n0, instanceRef)
return n0
},
)
},
},
)
},
}).render()

expect(html()).toBe(`<!--async component--><!--if-->`)

resolve!(
defineVaporComponent({
name: 'Foo',
setup(_, { expose }) {
const count = ref(0)
expose({
inc: () => {
count.value++
},
})

const n0 = template(`<p> </p>`)() as any
const x0 = child(n0) as any
renderEffect(() => {
setText(x0, String(count.value))
})
return n0
},
}),
)

await timeout()
// resolved
expect(html()).toBe(`<p>0</p><!--async component--><!--if-->`)

// change state + toggle out
instanceRef.value.inc()
toggle.value = false
await nextTick()
expect(html()).toBe('<!--if-->')

// toggle in, state should be maintained
toggle.value = true
await nextTick()
expect(html()).toBe('<p>1</p><!--async component--><!--if-->')

toggle.value = false
await nextTick()
expect(html()).toBe('<!--if-->')
})

test('handle error in async onActivated', async () => {
const err = new Error('foo')
Expand Down Expand Up @@ -1193,7 +1269,39 @@ describe('VaporKeepAlive', () => {
})

describe('vdom interop', () => {
test('render vdom component', async () => {
test('should work', () => {
const VdomComp = {
setup() {
onBeforeMount(() => oneHooks.beforeMount())
onMounted(() => oneHooks.mounted())
onActivated(() => oneHooks.activated())
onDeactivated(() => oneHooks.deactivated())
onUnmounted(() => oneHooks.unmounted())
return () => h('div', null, 'hi')
},
}

const App = defineVaporComponent({
setup() {
return createComponent(VaporKeepAlive, null, {
default: () => {
return createComponent(VdomComp)
},
})
},
})

const container = document.createElement('div')
document.body.appendChild(container)
const app = createVaporApp(App)
app.use(vaporInteropPlugin)
app.mount(container)

expect(container.innerHTML).toBe(`<div>hi</div>`)
assertHookCalls(oneHooks, [1, 1, 1, 0, 0])
})

test('with v-if', async () => {
const VdomComp = {
setup() {
const msg = ref('vdom')
Expand Down
14 changes: 11 additions & 3 deletions packages/runtime-vapor/src/apiDefineAsyncComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
createAsyncComponentContext,
currentInstance,
handleError,
isKeepAlive,
markAsyncBoundary,
performAsyncHydrate,
useAsyncComponentState,
Expand All @@ -28,6 +29,7 @@ import {
import { invokeArrayFns } from '@vue/shared'
import { insert, remove } from './block'
import { parentNode } from './dom/node'
import type { KeepAliveInstance } from './components/KeepAlive'

/*@ __NO_SIDE_EFFECTS__ */
export function defineVaporAsyncComponent<T extends VaporComponent>(
Expand Down Expand Up @@ -120,7 +122,7 @@ export function defineVaporAsyncComponent<T extends VaporComponent>(
// already resolved
let resolvedComp = getResolvedComp()
if (resolvedComp) {
frag!.update(() => createInnerComp(resolvedComp!, instance))
frag!.update(() => createInnerComp(resolvedComp!, instance, frag))
return frag
}

Expand All @@ -147,8 +149,6 @@ export function defineVaporAsyncComponent<T extends VaporComponent>(
load()
.then(() => {
loaded.value = true
// TODO parent is keep-alive, force update so the loaded component's
// name is taken into account
})
.catch(err => {
onError(err)
Expand Down Expand Up @@ -189,6 +189,14 @@ function createInnerComp(
appContext,
)

if (parent.parent && isKeepAlive(parent.parent)) {
// If there is a parent KeepAlive, let it handle the resolved async component
// This will process shapeFlag and cache the component
;(parent.parent as KeepAliveInstance).cacheComponent(instance)
// cache the wrapper instance as well
;(parent.parent as KeepAliveInstance).cacheComponent(parent)
}

// set ref
// @ts-expect-error
frag && frag.setRef && frag.setRef(instance)
Expand Down
5 changes: 5 additions & 0 deletions packages/runtime-vapor/src/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ export function remove(block: Block, parent?: ParentNode): void {
if (block.anchor) remove(block.anchor, parent)
if ((block as DynamicFragment).scope) {
;(block as DynamicFragment).scope!.stop()
const scopes = (block as DynamicFragment).keptAliveScopes
if (scopes) {
scopes.forEach(scope => scope.stop())
scopes.clear()
}
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions packages/runtime-vapor/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ import {
} from './dom/hydration'
import { _next, createElement } from './dom/node'
import { type TeleportFragment, isVaporTeleport } from './components/Teleport'
import type { KeepAliveInstance } from './components/KeepAlive'
import {
type KeepAliveInstance,
findParentKeepAlive,
} from './components/KeepAlive'
import {
insertionAnchor,
insertionParent,
Expand Down Expand Up @@ -688,7 +691,7 @@ export function mountComponent(
anchor?: Node | null | 0,
): void {
if (instance.shapeFlag! & ShapeFlags.COMPONENT_KEPT_ALIVE) {
;(instance.parent as KeepAliveInstance).activate(instance, parent, anchor)
findParentKeepAlive(instance)!.activate(instance, parent, anchor)
return
}

Expand Down Expand Up @@ -723,7 +726,7 @@ export function unmountComponent(
instance.parent.vapor &&
instance.shapeFlag! & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
) {
;(instance.parent as KeepAliveInstance).deactivate(instance)
findParentKeepAlive(instance)!.deactivate(instance)
return
}

Expand Down
Loading
Loading