Skip to content

Commit 02b4629

Browse files
committed
wip: save
1 parent f966349 commit 02b4629

File tree

3 files changed

+157
-52
lines changed

3 files changed

+157
-52
lines changed

packages/runtime-vapor/src/block.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,14 @@ export function remove(block: Block, parent?: ParentNode): void {
149149
if (block.anchor) remove(block.anchor, parent)
150150
if ((block as DynamicFragment).scope) {
151151
;(block as DynamicFragment).scope!.stop()
152+
153+
const pausedScopes = (block as DynamicFragment).pausedScopes
154+
if (pausedScopes) {
155+
for (let i = 0; i < pausedScopes.length; i++) {
156+
pausedScopes[i].stop()
157+
}
158+
pausedScopes.length = 0
159+
}
152160
}
153161
}
154162
}

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

Lines changed: 143 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
type GenericComponentInstance,
33
type KeepAliveProps,
4+
type VNode,
45
currentInstance,
56
devtoolsComponentAdded,
67
getComponentName,
@@ -25,7 +26,11 @@ import {
2526
import { defineVaporComponent } from '../apiDefineComponent'
2627
import { ShapeFlags, invokeArrayFns, isArray } from '@vue/shared'
2728
import { createElement } from '../dom/node'
28-
import { type VaporFragment, isFragment } from '../fragment'
29+
import {
30+
type DynamicFragment,
31+
type VaporFragment,
32+
isFragment,
33+
} from '../fragment'
2934

3035
export interface KeepAliveInstance extends VaporComponentInstance {
3136
activate: (
@@ -34,15 +39,16 @@ export interface KeepAliveInstance extends VaporComponentInstance {
3439
anchor?: Node | null | 0,
3540
) => void
3641
deactivate: (instance: VaporComponentInstance) => void
37-
process: (block: Block) => void
3842
cacheComponent: (instance: VaporComponentInstance) => void
3943
getCachedComponent: (
4044
comp: VaporComponent,
4145
) => VaporComponentInstance | VaporFragment | undefined
4246
getStorageContainer: () => ParentNode
47+
processFragment: (fragment: DynamicFragment) => void
48+
cacheFragment: (fragment: DynamicFragment) => void
4349
}
4450

45-
type CacheKey = VaporComponent
51+
type CacheKey = VaporComponent | VNode['type']
4652
type Cache = Map<CacheKey, VaporComponentInstance | VaporFragment>
4753
type Keys = Set<CacheKey>
4854

@@ -116,54 +122,55 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
116122
const innerBlock = getInnerBlock(block)!
117123
if (!innerBlock || !shouldCache(innerBlock)) return
118124

119-
const key = innerBlock.type
120-
const blockToCache =
121-
isFragment(block) && isVdomInteropFragment(block.nodes)
122-
? // cache the fragment nodes for vdom interop
123-
block.nodes
124-
: isVdomInteropFragment(block)
125-
? block
126-
: innerBlock
127-
128-
innerCacheBlock(key, blockToCache)
125+
let toCache: VaporComponentInstance | VaporFragment
126+
let key: CacheKey
127+
let frag: VaporFragment | undefined
128+
if (isFragment(block) && (frag = findInteropFragment(block))) {
129+
// vdom component: cache the fragment
130+
toCache = frag
131+
key = getCacheKey(frag)
132+
} else {
133+
// vapor component: cache the instance
134+
toCache = innerBlock
135+
key = innerBlock.type
136+
}
137+
innerCacheBlock(key, toCache)
129138
}
130139

131140
onMounted(cacheBlock)
132141
onUpdated(cacheBlock)
133142

134143
onBeforeUnmount(() => {
135-
cache.forEach(item => {
136-
const cached = getInnerComponent(item)!
137-
resetShapeFlag(cached)
138-
cache.delete(cached.type)
144+
cache.forEach((cached, key) => {
145+
const instance = getInstanceFromCache(cached)
146+
if (!instance) return
147+
148+
resetCachedShapeFlag(cached)
149+
cache.delete(key)
150+
139151
// current instance will be unmounted as part of keep-alive's unmount
140152
if (current) {
141-
const innerComp = getInnerComponent(current)!
142-
if (innerComp.type === cached.type) {
143-
const instance = cached.vapor
144-
? cached
145-
: // vdom interop
146-
(cached as any).component
153+
const currentKey = getCacheKey(current)
154+
if (currentKey === key) {
155+
// call deactivated hook
147156
const da = instance.da
148157
da && queuePostFlushCb(da)
149158
return
150159
}
151160
}
152-
remove(item, storageContainer)
161+
162+
remove(cached, storageContainer)
153163
})
154164
})
155165

156166
keepAliveInstance.getStorageContainer = () => storageContainer
167+
157168
keepAliveInstance.getCachedComponent = comp => {
158169
// For async components, use the resolved component type as the cache key
159-
const key = (comp as any).__asyncResolved || comp
160-
return cache.get(key)
170+
return cache.get(comp.__asyncResolved || comp)
161171
}
162172

163-
const processShapeFlag = (keepAliveInstance.process = block => {
164-
const instance = getInnerComponent(block)
165-
if (!instance) return
166-
173+
const setShapeFlags = (instance: VaporComponentInstance) => {
167174
// For unresolved async wrappers, skip processing
168175
// Wait for resolution and re-process via createInnerComp
169176
if (isAsyncWrapper(instance) && !instance.type.__asyncResolved) {
@@ -177,14 +184,51 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
177184
if (shouldCache(instance)) {
178185
instance.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
179186
}
180-
})
187+
}
181188

182189
keepAliveInstance.cacheComponent = (instance: VaporComponentInstance) => {
183190
if (!shouldCache(instance)) return
184-
processShapeFlag(instance)
191+
setShapeFlags(instance)
185192
innerCacheBlock(instance.type, instance)
186193
}
187194

195+
keepAliveInstance.processFragment = (frag: DynamicFragment) => {
196+
const innerBlock = getInnerBlock(frag.nodes)
197+
if (!innerBlock) return
198+
199+
const fragment = findInteropFragment(frag.nodes)
200+
if (fragment) {
201+
setVdomShapeFlags(fragment)
202+
} else {
203+
setShapeFlags(innerBlock)
204+
}
205+
}
206+
207+
keepAliveInstance.cacheFragment = (fragment: DynamicFragment) => {
208+
// Find the component within the fragment
209+
const innerBlock = getInnerBlock(fragment.nodes)
210+
if (!innerBlock || !shouldCache(innerBlock)) return
211+
212+
// Determine what to cache based on fragment type
213+
let toCache: VaporComponentInstance | VaporFragment
214+
let key: CacheKey
215+
216+
// find vdom interop fragment
217+
const frag = findInteropFragment(fragment)
218+
if (frag) {
219+
// For vdom components, set shapeFlag
220+
setVdomShapeFlags(frag)
221+
toCache = frag
222+
key = getCacheKey(frag)!
223+
} else {
224+
setShapeFlags(innerBlock)
225+
toCache = innerBlock
226+
key = innerBlock.type
227+
}
228+
229+
innerCacheBlock(key, toCache)
230+
}
231+
188232
keepAliveInstance.activate = (instance, parentNode, anchor) => {
189233
current = instance
190234
activate(instance, parentNode, anchor)
@@ -194,6 +238,34 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
194238
deactivate(instance, storageContainer)
195239
}
196240

241+
function setVdomShapeFlags(
242+
fragment: VaporFragment,
243+
shouldKeepAlive: boolean = true,
244+
) {
245+
if (shouldKeepAlive) {
246+
fragment.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
247+
}
248+
const fragKey = getCacheKey(fragment)
249+
if (fragKey && cache.has(fragKey)) {
250+
fragment.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
251+
}
252+
// Also set shapeFlag on the component instance if it exists
253+
const vnode = fragment.vnode as any
254+
if (vnode && vnode.component) {
255+
vnode.component.shapeFlag = fragment.vnode!.shapeFlag
256+
}
257+
}
258+
259+
function resetCachedShapeFlag(
260+
cached: VaporComponentInstance | VaporFragment,
261+
) {
262+
if (isVaporComponent(cached)) {
263+
resetShapeFlag(cached)
264+
} else {
265+
resetShapeFlag(cached.vnode)
266+
}
267+
}
268+
197269
let children = slots.default()
198270
if (isArray(children) && children.length > 1) {
199271
if (__DEV__) {
@@ -202,18 +274,18 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
202274
return children
203275
}
204276

205-
// `children` could be either a `VaporComponentInstance` or a `DynamicFragment`
206-
// (when using `v-if` or `<component is/>`). For `DynamicFragment` children,
207-
// the `shapeFlag` is processed in `DynamicFragment.update`. Here only need
208-
// to process the `VaporComponentInstance`
209-
if (isVaporComponent(children)) processShapeFlag(children)
210-
else if (isVdomInteropFragment(children)) {
211-
children.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
277+
// Process shapeFlag for vapor and vdom components
278+
// DynamicFragment (v-if, <component is/>) is processed in DynamicFragment.update
279+
if (isVaporComponent(children)) {
280+
setShapeFlags(children)
281+
} else if (isInteropFragment(children)) {
282+
setVdomShapeFlags(children, true)
212283
}
213284

214285
function pruneCache(filter: (name: string) => boolean) {
215-
cache.forEach((instance, key) => {
216-
instance = getInnerComponent(instance)!
286+
cache.forEach((cached, key) => {
287+
const instance = getInstanceFromCache(cached)
288+
if (!instance) return
217289
const name = getComponentName(instance.type)
218290
if (name && !filter(name)) {
219291
pruneCacheEntry(key)
@@ -223,7 +295,9 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
223295

224296
function pruneCacheEntry(key: CacheKey) {
225297
const cached = cache.get(key)!
226-
resetShapeFlag(cached)
298+
299+
resetCachedShapeFlag(cached)
300+
227301
// don't unmount if the instance is the current one
228302
if (cached !== current) {
229303
remove(cached)
@@ -251,25 +325,44 @@ function getInnerBlock(block: Block): VaporComponentInstance | undefined {
251325
if (isVaporComponent(block)) {
252326
return block
253327
}
254-
if (isVdomInteropFragment(block)) {
328+
if (isInteropFragment(block)) {
255329
return block.vnode as any
256330
}
257331
if (isFragment(block)) {
258332
return getInnerBlock(block.nodes)
259333
}
260334
}
261335

262-
function getInnerComponent(block: Block): VaporComponentInstance | undefined {
263-
if (isVaporComponent(block)) {
336+
function isInteropFragment(block: Block): block is VaporFragment {
337+
return !!(isFragment(block) && block.vnode)
338+
}
339+
340+
function findInteropFragment(block: Block): VaporFragment | undefined {
341+
if (isInteropFragment(block)) {
264342
return block
265-
} else if ((block as any as GenericComponentInstance).vnode) {
266-
// vdom interop
267-
return (block as any as GenericComponentInstance).vnode as any
343+
}
344+
if (isFragment(block)) {
345+
return findInteropFragment(block.nodes)
268346
}
269347
}
270348

271-
function isVdomInteropFragment(block: Block): block is VaporFragment {
272-
return !!(isFragment(block) && block.insert)
349+
function getInstanceFromCache(
350+
cached: VaporComponentInstance | VaporFragment,
351+
): GenericComponentInstance {
352+
if (isVaporComponent(cached)) {
353+
return cached
354+
}
355+
// vdom interop
356+
return cached.vnode!.component as GenericComponentInstance
357+
}
358+
359+
function getCacheKey(block: VaporComponentInstance | VaporFragment): CacheKey {
360+
if (isVaporComponent(block)) {
361+
return block.type
362+
}
363+
364+
// vdom interop
365+
return block.vnode!.type
273366
}
274367

275368
export function activate(

packages/runtime-vapor/src/fragment.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export class DynamicFragment extends VaporFragment {
7373
current?: BlockFn
7474
fallback?: BlockFn
7575
anchorLabel?: string
76+
pausedScopes?: EffectScope[]
7677

7778
constructor(anchorLabel?: string) {
7879
super([])
@@ -100,7 +101,10 @@ export class DynamicFragment extends VaporFragment {
100101
// teardown previous branch
101102
if (this.scope) {
102103
if (isKeepAlive(instance)) {
103-
;(instance as KeepAliveInstance).process(this.nodes)
104+
;(instance as KeepAliveInstance).processFragment(this)
105+
// Pause the scope and store it for later cleanup
106+
this.scope.pause()
107+
;(this.pausedScopes || (this.pausedScopes = [])).push(this.scope)
104108
} else {
105109
this.scope.stop()
106110
}
@@ -159,7 +163,7 @@ export class DynamicFragment extends VaporFragment {
159163
this.scope = new EffectScope()
160164
this.nodes = this.scope.run(render) || []
161165
if (isKeepAlive(instance)) {
162-
;(instance as KeepAliveInstance).process(this.nodes)
166+
;(instance as KeepAliveInstance).cacheFragment(this)
163167
}
164168
if (transition) {
165169
this.$transition = applyTransitionHooks(this.nodes, transition)

0 commit comments

Comments
 (0)