2
0
Эх сурвалжийг харах

perf(runtime-vapor): tree-shake unused keep-alive paths

daiwei 2 өдөр өмнө
parent
commit
466ec98c36

+ 2 - 0
packages/runtime-vapor/src/apiCreateDynamicComponent.ts

@@ -32,6 +32,7 @@ import {
 import { DynamicFragment, type VaporFragment } from './fragment'
 import type { KeepAliveInstance } from './components/KeepAlive'
 import { isInteropEnabled } from './vdomInteropState'
+import { enableKeepAlive } from './keepAlive'
 
 export function createDynamicComponent(
   getter: () => any,
@@ -62,6 +63,7 @@ export function createDynamicComponent(
       // Handles VNodes passed from VDOM components (e.g., `h(VaporComp)` from slots)
       if (isInteropEnabled && appContext.vapor && isVNode(value)) {
         if (isKeepAlive(currentInstance)) {
+          enableKeepAlive()
           const frag = (
             currentInstance as KeepAliveInstance
           ).ctx.getCachedComponent(value.type, value.key) as VaporFragment

+ 4 - 1
packages/runtime-vapor/src/apiDefineAsyncComponent.ts

@@ -27,6 +27,7 @@ import {
 } from './dom/hydration'
 import type { TransitionOptions } from './block'
 import { _next } from './dom/node'
+import { isKeepAliveEnabled } from './keepAlive'
 
 /*@ __NO_SIDE_EFFECTS__ */
 export function defineVaporAsyncComponent<T extends VaporComponent>(
@@ -185,7 +186,9 @@ export function defineVaporAsyncComponent<T extends VaporComponent>(
 
         frag.update(render)
         // Manually trigger cacheBlock for KeepAlive
-        if (frag.keepAliveCtx) frag.keepAliveCtx.cacheBlock()
+        if (isKeepAliveEnabled && frag.keepAliveCtx) {
+          frag.keepAliveCtx.cacheBlock()
+        }
       })
 
       return frag

+ 14 - 3
packages/runtime-vapor/src/component.ts

@@ -108,8 +108,9 @@ import {
 import type { KeepAliveInstance } from './components/KeepAlive'
 import {
   currentKeepAliveCtx,
+  isKeepAliveEnabled,
   setCurrentKeepAliveCtx,
-} from './components/KeepAlive'
+} from './keepAlive'
 import {
   insertionAnchor,
   insertionParent,
@@ -313,6 +314,7 @@ export function createComponent(
 
     // keep-alive
     if (
+      isKeepAliveEnabled &&
       currentInstance &&
       currentInstance.vapor &&
       isKeepAlive(currentInstance)
@@ -374,7 +376,11 @@ export function createComponent(
     // handle currentKeepAliveCtx for component boundary isolation
     // AsyncWrapper should NOT clear currentKeepAliveCtx so its internal
     // DynamicFragment can capture it
-    if (currentKeepAliveCtx && !isAsyncWrapper(instance)) {
+    if (
+      isKeepAliveEnabled &&
+      currentKeepAliveCtx &&
+      !isAsyncWrapper(instance)
+    ) {
       currentKeepAliveCtx.processShapeFlag(instance)
       // clear currentKeepAliveCtx so child components don't associate
       // with parent's KeepAlive
@@ -1006,7 +1012,10 @@ export function mountComponent(
     return
   }
 
-  if (instance.shapeFlag! & ShapeFlags.COMPONENT_KEPT_ALIVE) {
+  if (
+    isKeepAliveEnabled &&
+    instance.shapeFlag! & ShapeFlags.COMPONENT_KEPT_ALIVE
+  ) {
     ;(instance.parent as KeepAliveInstance)!.ctx.activate(
       instance,
       parent,
@@ -1034,6 +1043,7 @@ export function mountComponent(
   }
   if (instance.m) queuePostFlushCb(instance.m!)
   if (
+    isKeepAliveEnabled &&
     instance.shapeFlag! & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE &&
     instance.a
   ) {
@@ -1051,6 +1061,7 @@ export function unmountComponent(
 ): void {
   // Skip unmount for kept-alive components - deactivate if called from remove()
   if (
+    isKeepAliveEnabled &&
     instance.shapeFlag! & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE &&
     instance.parent &&
     instance.parent.vapor &&

+ 7 - 31
packages/runtime-vapor/src/components/KeepAlive.ts

@@ -36,36 +36,12 @@ import { unsetRef } from '../refCleanup'
 import { type VaporFragment, isDynamicFragment, isFragment } from '../fragment'
 import type { EffectScope } from '@vue/reactivity'
 import { isInteropEnabled } from '../vdomInteropState'
-
-export interface VaporKeepAliveContext {
-  processShapeFlag(block: Block): CacheKey | false
-  cacheBlock(block?: Block): void
-  cacheScope(cacheKey: CacheKey, scopeLookupKey: any, scope: EffectScope): void
-  getScope(key: any): EffectScope | undefined
-}
-
-export let currentKeepAliveCtx: VaporKeepAliveContext | null = null
-
-export function setCurrentKeepAliveCtx(
-  ctx: VaporKeepAliveContext | null,
-): VaporKeepAliveContext | null {
-  try {
-    return currentKeepAliveCtx
-  } finally {
-    currentKeepAliveCtx = ctx
-  }
-}
-
-let currentCacheKey: any | undefined
-export function withCurrentCacheKey<T>(key: any, fn: () => T): T {
-  const prev = currentCacheKey
-  currentCacheKey = key
-  try {
-    return fn()
-  } finally {
-    currentCacheKey = prev
-  }
-}
+import {
+  type VaporKeepAliveContext,
+  currentCacheKey,
+  setCurrentKeepAliveCtx,
+  withKeepAliveEnabled,
+} from '../keepAlive'
 
 export interface KeepAliveInstance extends VaporComponentInstance {
   ctx: {
@@ -416,7 +392,7 @@ const VaporKeepAliveImpl = defineVaporComponent({
 })
 
 export const VaporKeepAlive: DefineVaporComponent<{}, string, KeepAliveProps> =
-  VaporKeepAliveImpl
+  /*@__PURE__*/ withKeepAliveEnabled(VaporKeepAliveImpl)
 
 const shouldCache = (
   block: GenericComponentInstance | VaporFragment,

+ 35 - 22
packages/runtime-vapor/src/fragment.ts

@@ -50,9 +50,10 @@ import { setBlockKey } from './helpers/setKey'
 import {
   type VaporKeepAliveContext,
   currentKeepAliveCtx,
+  isKeepAliveEnabled,
   setCurrentKeepAliveCtx,
   withCurrentCacheKey,
-} from './components/KeepAlive'
+} from './keepAlive'
 import {
   applyTransitionHooks,
   applyTransitionLeaveHooks,
@@ -94,24 +95,32 @@ export class VaporFragment<
   // render context
   readonly renderInstance: GenericComponentInstance | null = currentInstance
   readonly slotOwner: VaporComponentInstance | null = currentSlotOwner
-  readonly keepAliveCtx: VaporKeepAliveContext | null = currentKeepAliveCtx
+  readonly keepAliveCtx?: VaporKeepAliveContext | null
   readonly inheritedSlotBoundary: SlotBoundaryContext | null =
     currentSlotBoundary
 
   constructor(nodes: T) {
     this.nodes = nodes
+    if (isKeepAliveEnabled) {
+      this.keepAliveCtx = currentKeepAliveCtx
+    }
   }
 
   protected runWithRenderCtx<R>(fn: () => R): R {
     const prevInstance = setCurrentInstance(this.renderInstance)
     const prevSlotOwner = setCurrentSlotOwner(this.slotOwner)
-    const prevKeepAliveCtx = setCurrentKeepAliveCtx(this.keepAliveCtx)
+    let prevKeepAliveCtx: VaporKeepAliveContext | null = null
+    if (isKeepAliveEnabled) {
+      prevKeepAliveCtx = setCurrentKeepAliveCtx(this.keepAliveCtx || null)
+    }
     const prevBoundary = setCurrentSlotBoundary(this.inheritedSlotBoundary)
     try {
       return fn()
     } finally {
       setCurrentSlotBoundary(prevBoundary)
-      setCurrentKeepAliveCtx(prevKeepAliveCtx)
+      if (isKeepAliveEnabled) {
+        setCurrentKeepAliveCtx(prevKeepAliveCtx)
+      }
       setCurrentSlotOwner(prevSlotOwner)
       setCurrentInstance(...prevInstance)
     }
@@ -213,24 +222,28 @@ export class DynamicFragment extends VaporFragment {
     const parent = isHydrating ? null : this.anchor.parentNode
     // teardown previous branch
     if (this.scope) {
-      let retainScope = false
-      const keepAliveCtx = this.keepAliveCtx
-
-      // if keepAliveCtx exists and processShapeFlag returns a cache key,
-      // cache the scope and retain it.
-      const cacheKey = keepAliveCtx
-        ? this.keyed
-          ? withCurrentCacheKey(this.current, () =>
-              keepAliveCtx.processShapeFlag(this.nodes),
-            )
-          : keepAliveCtx.processShapeFlag(this.nodes)
-        : false
-      if (cacheKey !== false) {
-        keepAliveCtx!.cacheScope(cacheKey, this.current, this.scope)
-        retainScope = true
-      }
+      if (isKeepAliveEnabled) {
+        let retainScope = false
+        const keepAliveCtx = this.keepAliveCtx
+
+        // if keepAliveCtx exists and processShapeFlag returns a cache key,
+        // cache the scope and retain it.
+        if (keepAliveCtx) {
+          const cacheKey = this.keyed
+            ? withCurrentCacheKey(this.current, () =>
+                keepAliveCtx.processShapeFlag(this.nodes),
+              )
+            : keepAliveCtx.processShapeFlag(this.nodes)
+          if (cacheKey !== false) {
+            keepAliveCtx.cacheScope(cacheKey, this.current, this.scope)
+            retainScope = true
+          }
+        }
 
-      if (!retainScope) {
+        if (!retainScope) {
+          this.scope.stop()
+        }
+      } else {
         this.scope.stop()
       }
       const mode = transition && transition.mode
@@ -309,7 +322,7 @@ export class DynamicFragment extends VaporFragment {
   ): void {
     this.current = key
     if (render) {
-      const keepAliveCtx = this.keepAliveCtx
+      const keepAliveCtx = isKeepAliveEnabled ? this.keepAliveCtx : null
       // try to reuse the kept-alive scope
       const scope = keepAliveCtx && keepAliveCtx.getScope(this.current)
       if (scope) {

+ 4 - 1
packages/runtime-vapor/src/helpers/setKey.ts

@@ -2,6 +2,7 @@ import { isArray } from '@vue/shared'
 import { isKeepAlive } from '@vue/runtime-dom'
 import type { Block } from '../block'
 import { isVaporComponent } from '../component'
+import { isKeepAliveEnabled } from '../keepAlive'
 
 export function setBlockKey(
   block: (Block & { $key?: any }) | null | undefined,
@@ -16,7 +17,9 @@ export function setBlockKey(
     // KeepAlive resolves cache keys from its child block. An outer wrapper key
     // (for example from v-if) must not override the child's own component type
     // or explicit key, otherwise cached branches will not be found again.
-    if (!isKeepAlive(block) && block.block) setBlockKey(block.block, key)
+    if ((!isKeepAliveEnabled || !isKeepAlive(block)) && block.block) {
+      setBlockKey(block.block, key)
+    }
   } else if (isArray(block)) {
     if (block.length === 1) {
       setBlockKey(block[0], key)

+ 2 - 1
packages/runtime-vapor/src/hmr.ts

@@ -16,6 +16,7 @@ import {
 } from './component'
 import { isArray } from '@vue/shared'
 import { isFragment } from './fragment'
+import { isKeepAliveEnabled } from './keepAlive'
 
 export function hmrRerender(instance: VaporComponentInstance): void {
   const normalized = normalizeBlock(instance.block)
@@ -38,7 +39,7 @@ export function hmrReload(
 ): void {
   // If parent is KeepAlive, rerender it so new component goes through
   // KeepAlive's slot rendering flow to receive activated hooks properly
-  if (instance.parent && isKeepAlive(instance.parent)) {
+  if (isKeepAliveEnabled && instance.parent && isKeepAlive(instance.parent)) {
     instance.parent.hmrRerender!()
     return
   }

+ 1 - 1
packages/runtime-vapor/src/index.ts

@@ -84,4 +84,4 @@ export { VaporTransitionGroup } from './components/TransitionGroup'
 export type { VaporComponent, VaporComponentOptions } from './component'
 export type { VaporSlot } from './componentSlots'
 export type { VaporTransitionHooks } from './block'
-export type { VaporKeepAliveContext } from './components/KeepAlive'
+export type { VaporKeepAliveContext } from './keepAlive'

+ 42 - 0
packages/runtime-vapor/src/keepAlive.ts

@@ -0,0 +1,42 @@
+import type { EffectScope } from '@vue/reactivity'
+import type { Block } from './block'
+
+export interface VaporKeepAliveContext {
+  processShapeFlag(block: Block): any | false
+  cacheBlock(block?: Block): void
+  cacheScope(cacheKey: any, scopeLookupKey: any, scope: EffectScope): void
+  getScope(key: any): EffectScope | undefined
+}
+
+export let isKeepAliveEnabled = false
+export let currentKeepAliveCtx: VaporKeepAliveContext | null = null
+export let currentCacheKey: any | undefined
+
+export function enableKeepAlive(): void {
+  isKeepAliveEnabled = true
+}
+
+export function withKeepAliveEnabled<T>(value: T): T {
+  enableKeepAlive()
+  return value
+}
+
+export function setCurrentKeepAliveCtx(
+  ctx: VaporKeepAliveContext | null,
+): VaporKeepAliveContext | null {
+  try {
+    return currentKeepAliveCtx
+  } finally {
+    currentKeepAliveCtx = ctx
+  }
+}
+
+export function withCurrentCacheKey<T>(key: any, fn: () => T): T {
+  const prev = currentCacheKey
+  currentCacheKey = key
+  try {
+    return fn()
+  } finally {
+    currentCacheKey = prev
+  }
+}

+ 17 - 6
packages/runtime-vapor/src/vdomInterop.ts

@@ -119,10 +119,14 @@ import { setInteropEnabled } from './vdomInteropState'
 import {
   type KeepAliveInstance,
   activate,
-  currentKeepAliveCtx,
   deactivate,
-  setCurrentKeepAliveCtx,
 } from './components/KeepAlive'
+import {
+  currentKeepAliveCtx,
+  enableKeepAlive,
+  isKeepAliveEnabled,
+  setCurrentKeepAliveCtx,
+} from './keepAlive'
 import {
   parentSuspense as currentParentSuspense,
   enableSuspense,
@@ -200,8 +204,10 @@ const vaporInteropImpl: Omit<
     ensureVNodeHookState(instance, vnode)
 
     // copy the shape flag from the vdom component if inside a keep-alive
-    if (parentComponent && isKeepAlive(parentComponent))
+    if (parentComponent && isKeepAlive(parentComponent)) {
+      enableKeepAlive()
       instance.shapeFlag = vnode.shapeFlag
+    }
 
     if (vnode.transition) {
       ensureTransitionHooksRegistered()
@@ -790,7 +796,7 @@ function createVDOMComponent(
   frag.$key = vnode.key
   trackFragmentVNodeUpdates(frag, vnode)
 
-  if (currentKeepAliveCtx) {
+  if (isKeepAliveEnabled && currentKeepAliveCtx) {
     currentKeepAliveCtx.processShapeFlag(frag)
     // for VDOM async components, trigger cacheBlock after resolution
     if ((component as any).__asyncLoader) {
@@ -1494,11 +1500,16 @@ const renderEmptyVNodes = (): VNodeArrayChildren => []
 // VDOM fallback cleanup follow a different lifecycle.
 function runWithFragmentRenderCtx<R>(fragment: VaporFragment, fn: () => R): R {
   const prevSlotOwner = setCurrentSlotOwner(fragment.slotOwner)
-  const prevKeepAliveCtx = setCurrentKeepAliveCtx(fragment.keepAliveCtx)
+  let prevKeepAliveCtx = null
+  if (isKeepAliveEnabled) {
+    prevKeepAliveCtx = setCurrentKeepAliveCtx(fragment.keepAliveCtx || null)
+  }
   try {
     return withOwnedSlotBoundary(fragment.inheritedSlotBoundary, fn)
   } finally {
-    setCurrentKeepAliveCtx(prevKeepAliveCtx)
+    if (isKeepAliveEnabled) {
+      setCurrentKeepAliveCtx(prevKeepAliveCtx)
+    }
     setCurrentSlotOwner(prevSlotOwner)
   }
 }