Skip to content

Commit 0f4d7fb

Browse files
authored
feat(runtime-vapor): vapor transition work with vapor async component (#14053)
1 parent d14c5a2 commit 0f4d7fb

File tree

5 files changed

+123
-27
lines changed

5 files changed

+123
-27
lines changed

packages-private/vapor-e2e-test/__tests__/transition.spec.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ const {
1313
nextFrame,
1414
timeout,
1515
isVisible,
16-
count,
1716
html,
1817
transitionStart,
1918
waitForElement,
@@ -40,6 +39,9 @@ describe('vapor transition', () => {
4039

4140
beforeEach(async () => {
4241
const baseUrl = `http://localhost:${port}/transition/`
42+
await page().evaluateOnNewDocument(dur => {
43+
;(window as any).__TRANSITION_DURATION__ = dur
44+
}, duration)
4345
await page().goto(baseUrl)
4446
await page().waitForSelector('#app')
4547
})
@@ -972,6 +974,65 @@ describe('vapor transition', () => {
972974
)
973975
})
974976

977+
describe('transition with AsyncComponent', () => {
978+
test('apply transition to inner component', async () => {
979+
const btnSelector = '.async > button'
980+
const containerSelector = '.async > div'
981+
982+
expect(await html(containerSelector)).toBe('')
983+
984+
// toggle
985+
await click(btnSelector)
986+
await nextTick()
987+
// not yet resolved
988+
expect(await html(containerSelector)).toBe('')
989+
990+
// wait resolving
991+
await timeout(50)
992+
993+
// enter (resolved)
994+
expect(await html(containerSelector)).toBe(
995+
'<div class="v-enter-from v-enter-active">vapor compA</div>',
996+
)
997+
await nextFrame()
998+
expect(await html(containerSelector)).toBe(
999+
'<div class="v-enter-active v-enter-to">vapor compA</div>',
1000+
)
1001+
await transitionFinish()
1002+
expect(await html(containerSelector)).toBe(
1003+
'<div class="">vapor compA</div>',
1004+
)
1005+
1006+
// leave
1007+
await click(btnSelector)
1008+
await nextTick()
1009+
expect(await html(containerSelector)).toBe(
1010+
'<div class="v-leave-from v-leave-active">vapor compA</div>',
1011+
)
1012+
await nextFrame()
1013+
expect(await html(containerSelector)).toBe(
1014+
'<div class="v-leave-active v-leave-to">vapor compA</div>',
1015+
)
1016+
await transitionFinish()
1017+
expect(await html(containerSelector)).toBe('')
1018+
1019+
// enter again
1020+
await click(btnSelector)
1021+
// use the already resolved component
1022+
expect(await html(containerSelector)).toBe(
1023+
'<div class="v-enter-from v-enter-active">vapor compA</div>',
1024+
)
1025+
await nextFrame()
1026+
expect(await html(containerSelector)).toBe(
1027+
'<div class="v-enter-active v-enter-to">vapor compA</div>',
1028+
)
1029+
await transitionFinish()
1030+
expect(await html(containerSelector)).toBe(
1031+
'<div class="">vapor compA</div>',
1032+
)
1033+
})
1034+
})
1035+
9751036
describe('transition with v-show', () => {
9761037
test(
9771038
'named transition with v-show',

packages-private/vapor-e2e-test/transition/App.vue

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import {
77
VaporTransition,
88
createIf,
99
template,
10+
defineVaporAsyncComponent,
1011
onUnmounted,
1112
} from 'vue'
1213
const show = ref(true)
1314
const toggle = ref(true)
1415
const count = ref(0)
1516
1617
const timeout = (fn, time) => setTimeout(fn, time)
17-
const duration = typeof process !== 'undefined' && process.env.CI ? 200 : 50
18+
const duration = window.__TRANSITION_DURATION__ || 50
1819
1920
let calls = {
2021
basic: [],
@@ -94,6 +95,10 @@ function changeViewInOut() {
9495
viewInOut.value = viewInOut.value === SimpleOne ? Two : SimpleOne
9596
}
9697
98+
const AsyncComp = defineVaporAsyncComponent(() => {
99+
return new Promise(resolve => setTimeout(() => resolve(VaporCompA), 50))
100+
})
101+
97102
const TrueBranch = defineVaporComponent({
98103
name: 'TrueBranch',
99104
setup() {
@@ -503,6 +508,17 @@ const click = () => {
503508
</div>
504509
<!-- mode end -->
505510

511+
<!-- async component -->
512+
<div class="async">
513+
<div id="container">
514+
<transition>
515+
<AsyncComp v-if="!toggle"></AsyncComp>
516+
</transition>
517+
</div>
518+
<button @click="toggle = !toggle">button</button>
519+
</div>
520+
<!-- async component end -->
521+
506522
<!-- with teleport -->
507523
<div class="with-teleport">
508524
<div class="target"></div>

packages/runtime-vapor/src/apiDefineAsyncComponent.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ import {
2626
removeFragmentNodes,
2727
} from './dom/hydration'
2828
import { invokeArrayFns } from '@vue/shared'
29-
import { insert, remove } from './block'
29+
import { type TransitionOptions, insert, remove } from './block'
3030
import { parentNode } from './dom/node'
31+
import { setTransitionHooks } from './components/Transition'
3132

3233
/*@ __NO_SIDE_EFFECTS__ */
3334
export function defineVaporAsyncComponent<T extends VaporComponent>(
@@ -109,7 +110,8 @@ export function defineVaporAsyncComponent<T extends VaporComponent>(
109110
},
110111

111112
setup() {
112-
const instance = currentInstance as VaporComponentInstance
113+
const instance = currentInstance as VaporComponentInstance &
114+
TransitionOptions
113115
markAsyncBoundary(instance)
114116

115117
const frag =
@@ -166,6 +168,8 @@ export function defineVaporAsyncComponent<T extends VaporComponent>(
166168
} else if (loadingComponent && !delayed.value) {
167169
render = () => createComponent(loadingComponent)
168170
}
171+
172+
if (instance.$transition) frag!.$transition = instance.$transition
169173
frag!.update(render)
170174
})
171175

@@ -176,10 +180,10 @@ export function defineVaporAsyncComponent<T extends VaporComponent>(
176180

177181
function createInnerComp(
178182
comp: VaporComponent,
179-
parent: VaporComponentInstance,
183+
parent: VaporComponentInstance & TransitionOptions,
180184
frag?: DynamicFragment,
181185
): VaporComponentInstance {
182-
const { rawProps, rawSlots, isSingleRoot, appContext } = parent
186+
const { rawProps, rawSlots, isSingleRoot, appContext, $transition } = parent
183187
const instance = createComponent(
184188
comp,
185189
rawProps,
@@ -189,6 +193,9 @@ function createInnerComp(
189193
appContext,
190194
)
191195

196+
// set transition hooks
197+
if ($transition) setTransitionHooks(instance, $transition)
198+
192199
// set ref
193200
// @ts-expect-error
194201
frag && frag.setRef && frag.setRef(instance)

packages/runtime-vapor/src/block.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,20 @@ export interface TransitionOptions {
3636
$transition?: VaporTransitionHooks
3737
}
3838

39-
export type TransitionBlock =
40-
| (Node & TransitionOptions)
41-
| (VaporFragment & TransitionOptions)
42-
| (DynamicFragment & TransitionOptions)
39+
export type TransitionBlock = (
40+
| Node
41+
| VaporFragment
42+
| DynamicFragment
43+
| VaporComponentInstance
44+
) &
45+
TransitionOptions
4346

44-
export type Block = TransitionBlock | VaporComponentInstance | Block[]
47+
export type Block =
48+
| Node
49+
| VaporFragment
50+
| DynamicFragment
51+
| VaporComponentInstance
52+
| Block[]
4553
export type BlockFn = (...args: any[]) => Block
4654

4755
export function isBlock(val: NonNullable<unknown>): val is Block {

packages/runtime-vapor/src/components/Transition.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
checkTransitionMode,
1212
currentInstance,
1313
getComponentName,
14+
isAsyncWrapper,
1415
isTemplateNode,
1516
leaveCbKey,
1617
queuePostFlushCb,
@@ -92,7 +93,7 @@ export const VaporTransition: FunctionalVaporComponent = /*@__PURE__*/ decorate(
9293
if (child) {
9394
// replace existing transition hooks
9495
child.$transition!.props = resolvedProps
95-
applyTransitionHooks(child, child.$transition!)
96+
applyTransitionHooks(child, child.$transition!, undefined, true)
9697
}
9798
}
9899
} else {
@@ -141,7 +142,7 @@ export const VaporTransition: FunctionalVaporComponent = /*@__PURE__*/ decorate(
141142
)
142143

143144
const getTransitionHooksContext = (
144-
key: String,
145+
key: string,
145146
props: TransitionProps,
146147
state: TransitionState,
147148
instance: GenericComponentInstance,
@@ -210,6 +211,7 @@ export function applyTransitionHooks(
210211
block: Block,
211212
hooks: VaporTransitionHooks,
212213
fallthroughAttrs: boolean = true,
214+
isResolved: boolean = false,
213215
): VaporTransitionHooks {
214216
// filter out comment nodes
215217
if (isArray(block)) {
@@ -222,7 +224,9 @@ export function applyTransitionHooks(
222224
}
223225

224226
const isFrag = isFragment(block)
225-
const child = findTransitionBlock(block, isFrag)
227+
const child = isResolved
228+
? (block as TransitionBlock)
229+
: findTransitionBlock(block, isFrag)
226230
if (!child) {
227231
// set transition hooks on fragment for reusing during it's updating
228232
if (isFrag) setTransitionHooksOnFragment(block, hooks)
@@ -238,7 +242,7 @@ export function applyTransitionHooks(
238242
hooks => (resolvedHooks = hooks as VaporTransitionHooks),
239243
)
240244
resolvedHooks.delayedLeave = delayedLeave
241-
setTransitionHooks(child, resolvedHooks)
245+
child.$transition = resolvedHooks
242246
if (isFrag) setTransitionHooksOnFragment(block, resolvedHooks)
243247

244248
// fallthrough attrs
@@ -266,7 +270,7 @@ export function applyTransitionLeaveHooks(
266270
state,
267271
instance,
268272
)
269-
setTransitionHooks(leavingBlock, leavingHooks)
273+
leavingBlock.$transition = leavingHooks
270274

271275
const { mode } = props
272276
if (mode === 'out-in') {
@@ -300,25 +304,25 @@ export function applyTransitionLeaveHooks(
300304
}
301305
}
302306

303-
const transitionBlockCache = new WeakMap<Block, TransitionBlock>()
304307
export function findTransitionBlock(
305308
block: Block,
306309
inFragment: boolean = false,
307310
): TransitionBlock | undefined {
308-
if (transitionBlockCache.has(block)) {
309-
return transitionBlockCache.get(block)
310-
}
311-
312311
let child: TransitionBlock | undefined
313312
if (block instanceof Node) {
314313
// transition can only be applied on Element child
315314
if (block instanceof Element) child = block
316315
} else if (isVaporComponent(block)) {
317-
// stop searching if encountering nested Transition component
318-
if (getComponentName(block.type) === displayName) return undefined
319-
child = findTransitionBlock(block.block, inFragment)
320-
// use component id as key
321-
if (child && child.$key === undefined) child.$key = block.uid
316+
// should save hooks on unresolved async wrapper, so that it can be applied after resolved
317+
if (isAsyncWrapper(block) && !block.type.__asyncResolved) {
318+
child = block
319+
} else {
320+
// stop searching if encountering nested Transition component
321+
if (getComponentName(block.type) === displayName) return undefined
322+
child = findTransitionBlock(block.block, inFragment)
323+
// use component id as key
324+
if (child && child.$key === undefined) child.$key = block.uid
325+
}
322326
} else if (isArray(block)) {
323327
let hasFound = false
324328
for (const c of block) {
@@ -369,7 +373,7 @@ export function setTransitionHooksOnFragment(
369373
}
370374

371375
export function setTransitionHooks(
372-
block: TransitionBlock | VaporComponentInstance,
376+
block: TransitionBlock,
373377
hooks: VaporTransitionHooks,
374378
): void {
375379
if (isVaporComponent(block)) {

0 commit comments

Comments
 (0)