From 6c4d22ac73c084a464148f98b7571154b6c09b01 Mon Sep 17 00:00:00 2001 From: daiwei Date: Fri, 11 Apr 2025 17:04:27 +0800 Subject: [PATCH] wip: vdom interop --- packages/runtime-vapor/src/block.ts | 9 +- packages/runtime-vapor/src/component.ts | 7 +- .../runtime-vapor/src/components/KeepAlive.ts | 116 +++++++++++++----- packages/runtime-vapor/src/vdomInterop.ts | 12 +- 4 files changed, 99 insertions(+), 45 deletions(-) diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index c9d81986b4..9693bb1adc 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -57,10 +57,7 @@ export class DynamicFragment extends VaporFragment { // teardown previous branch if (this.scope) { if (isKeepAlive(instance)) { - ;(instance as KeepAliveInstance).process( - this.nodes as VaporComponentInstance, - ) - unmountComponent(this.nodes as VaporComponentInstance) + ;(instance as KeepAliveInstance).process(this.nodes) } else { this.scope.stop() } @@ -71,9 +68,7 @@ export class DynamicFragment extends VaporFragment { this.scope = new EffectScope() this.nodes = this.scope.run(render) || [] if (isKeepAlive(instance)) { - ;(instance as KeepAliveInstance).process( - this.nodes as VaporComponentInstance, - ) + ;(instance as KeepAliveInstance).process(this.nodes) } if (parent) insert(this.nodes, parent, this.anchor) } else { diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 957ad1f99f..278bde8ab2 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -154,10 +154,10 @@ export function createComponent( // try to get the cached instance if inside keep-alive if (currentInstance && isKeepAlive(currentInstance)) { - const cached = (currentInstance as KeepAliveInstance).getCachedInstance( + const cached = (currentInstance as KeepAliveInstance).getCachedComponent( component, ) - if (cached) return cached + if (cached) return cached as any } // vdom interop enabled and component is not an explicit vapor component @@ -517,9 +517,8 @@ export function mountComponent( ;(instance.parent as KeepAliveInstance).activate( instance, parentNode, - anchor as any, + anchor, ) - instance.isMounted = true return } diff --git a/packages/runtime-vapor/src/components/KeepAlive.ts b/packages/runtime-vapor/src/components/KeepAlive.ts index b89a559fe6..439f0f3ff8 100644 --- a/packages/runtime-vapor/src/components/KeepAlive.ts +++ b/packages/runtime-vapor/src/components/KeepAlive.ts @@ -12,13 +12,18 @@ import { warn, watch, } from '@vue/runtime-dom' -import { type Block, insert, isFragment } from '../block' +import { + type Block, + type VaporFragment, + insert, + isFragment, + remove, +} from '../block' import { type ObjectVaporComponent, type VaporComponent, type VaporComponentInstance, isVaporComponent, - unmountComponent, } from '../component' import { defineVaporComponent } from '../apiDefineComponent' import { ShapeFlags, invokeArrayFns, isArray } from '@vue/shared' @@ -26,19 +31,19 @@ import { createElement } from '../dom/node' export interface KeepAliveInstance extends VaporComponentInstance { activate: ( - instance: VaporComponentInstance, + block: VaporComponentInstance | VaporFragment, parentNode: ParentNode, - anchor: Node, + anchor?: Node | null | 0, ) => void - deactivate: (instance: VaporComponentInstance) => void - process: (instance: VaporComponentInstance) => void - getCachedInstance: ( + deactivate: (block: VaporComponentInstance | VaporFragment) => void + process: (block: Block) => void + getCachedComponent: ( comp: VaporComponent, - ) => VaporComponentInstance | undefined + ) => VaporComponentInstance | VaporFragment | undefined } type CacheKey = VaporComponent -type Cache = Map +type Cache = Map type Keys = Set export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({ @@ -58,7 +63,7 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({ const cache: Cache = new Map() const keys: Keys = new Set() const storageContainer = createElement('div') - let current: VaporComponentInstance | undefined + let current: VaporComponentInstance | VaporFragment | undefined if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { ;(keepAliveInstance as any).__v_cache = cache @@ -76,7 +81,8 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({ function cacheBlock() { const { max } = props // TODO suspense - const innerBlock = getInnerBlock(keepAliveInstance.block!)! + const block = keepAliveInstance.block! + const innerBlock = getInnerBlock(block)! if (!innerBlock || !shouldCache(innerBlock)) return const key = innerBlock.type @@ -91,32 +97,43 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({ pruneCacheEntry(keys.values().next().value!) } } - cache.set(key, (current = innerBlock)) + cache.set( + key, + (current = + isFragment(block) && isFragment(block.nodes) + ? // cache the fragment nodes for vdom interop + block.nodes + : innerBlock), + ) } onMounted(cacheBlock) onUpdated(cacheBlock) onBeforeUnmount(() => { - cache.forEach(cached => { + cache.forEach(item => { + const cached = getInnerComponent(item)! resetShapeFlag(cached) cache.delete(cached.type) // current instance will be unmounted as part of keep-alive's unmount - if (current && current.type === cached.type) { - const da = cached.da - da && queuePostFlushCb(da) - return + if (current) { + const innerComp = getInnerComponent(current)! + if (innerComp.type === cached.type) { + const da = cached.da + da && queuePostFlushCb(da) + return + } } - unmountComponent(cached, storageContainer) + remove(cached, storageContainer) }) }) - keepAliveInstance.getCachedInstance = (comp: VaporComponent) => - cache.get(comp) + keepAliveInstance.getCachedComponent = comp => cache.get(comp) + + const process = (keepAliveInstance.process = block => { + const instance = getInnerComponent(block) + if (!instance) return - const process = (keepAliveInstance.process = ( - instance: VaporComponentInstance, - ) => { if (cache.has(instance.type)) { instance.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE } @@ -126,13 +143,19 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({ } }) - keepAliveInstance.activate = ( - instance: VaporComponentInstance, - parentNode: ParentNode, - anchor: Node, - ) => { - current = instance - insert(instance.block, parentNode, anchor) + keepAliveInstance.activate = (block, parentNode, anchor) => { + current = block + let instance + if (isVaporComponent(block)) { + instance = block + insert(block.block, parentNode, anchor) + } else { + // vdom interop + const comp = block.nodes as any + insert(comp.el, parentNode, anchor) + instance = comp.component + } + queuePostFlushCb(() => { instance.isDeactivated = false if (instance.a) invokeArrayFns(instance.a) @@ -143,8 +166,18 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({ } } - keepAliveInstance.deactivate = (instance: VaporComponentInstance) => { - insert(instance.block, storageContainer) + keepAliveInstance.deactivate = block => { + let instance + if (isVaporComponent(block)) { + instance = block + insert(block.block, storageContainer) + } else { + // vdom interop + const comp = block.nodes as any + insert(comp.el, storageContainer) + instance = comp.component + } + queuePostFlushCb(() => { if (instance.da) invokeArrayFns(instance.da) instance.isDeactivated = true @@ -171,6 +204,7 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({ function pruneCache(filter: (name: string) => boolean) { cache.forEach((instance, key) => { + instance = getInnerComponent(instance)! const name = getComponentName(instance.type) if (name && !filter(name)) { pruneCacheEntry(key) @@ -184,7 +218,7 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({ resetShapeFlag(cached) // don't unmount if the instance is the current one if (cached !== current) { - unmountComponent(cached) + remove(cached) } } cache.delete(key) @@ -210,7 +244,23 @@ function getInnerBlock(block: Block): VaporComponentInstance | undefined { if (isVaporComponent(block)) { return block } + if (isVdomInteropFragment(block)) { + return block.nodes as any + } if (isFragment(block)) { return getInnerBlock(block.nodes) } } + +function getInnerComponent(block: Block): VaporComponentInstance | undefined { + if (isVaporComponent(block)) { + return block + } else if (isVdomInteropFragment(block)) { + // vdom interop + return block.nodes as any + } +} + +function isVdomInteropFragment(block: Block): block is VaporFragment { + return !!(isFragment(block) && block.insert) +} diff --git a/packages/runtime-vapor/src/vdomInterop.ts b/packages/runtime-vapor/src/vdomInterop.ts index 77228fd72a..145c81e123 100644 --- a/packages/runtime-vapor/src/vdomInterop.ts +++ b/packages/runtime-vapor/src/vdomInterop.ts @@ -27,12 +27,13 @@ import { unmountComponent, } from './component' import { type Block, VaporFragment, insert, remove } from './block' -import { EMPTY_OBJ, extend, isFunction } from '@vue/shared' +import { EMPTY_OBJ, ShapeFlags, extend, isFunction } from '@vue/shared' import { type RawProps, rawPropsProxyHandlers } from './componentProps' import type { RawSlots, VaporSlot } from './componentSlots' import { renderEffect } from './renderEffect' import { createTextNode } from './dom/node' import { optimizePropertyLookup } from './dom/prop' +import type { KeepAliveInstance } from './components/KeepAlive' // mounting vapor components and slots in vdom const vaporInteropImpl: Omit< @@ -172,10 +173,18 @@ function createVDOMComponent( let isMounted = false const parentInstance = currentInstance as VaporComponentInstance const unmount = (parentNode?: ParentNode) => { + if (vnode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) { + ;(parentInstance as KeepAliveInstance).deactivate(frag) + return + } internals.umt(vnode.component!, null, !!parentNode) } frag.insert = (parentNode, anchor) => { + if (vnode.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) { + ;(parentInstance as KeepAliveInstance).activate(frag, parentNode, anchor) + return + } if (!isMounted) { internals.mt( vnode, @@ -201,6 +210,7 @@ function createVDOMComponent( } frag.remove = unmount + frag.nodes = vnode as any return frag } -- 2.47.2