소스 검색

chore(runtime-vapor): improve KeepAlive and TransitionGroup tree-shaking

avoid format impl function
daiwei 4 달 전
부모
커밋
e1e38a3631
2개의 변경된 파일304개의 추가작업 그리고 304개의 파일을 삭제
  1. 191 193
      packages/runtime-vapor/src/components/KeepAlive.ts
  2. 113 111
      packages/runtime-vapor/src/components/TransitionGroup.ts

+ 191 - 193
packages/runtime-vapor/src/components/KeepAlive.ts

@@ -54,234 +54,232 @@ type CacheKey = VaporComponent | VNode['type']
 type Cache = Map<CacheKey, VaporComponentInstance | VaporFragment>
 type Keys = Set<CacheKey>
 
-export const VaporKeepAliveImpl: ObjectVaporComponent =
-  /*@__PURE__*/ defineVaporComponent({
-    name: 'VaporKeepAlive',
-    __isKeepAlive: true,
-    props: {
-      include: [String, RegExp, Array],
-      exclude: [String, RegExp, Array],
-      max: [String, Number],
-    },
-    setup(props: KeepAliveProps, { slots }) {
-      if (!slots.default) {
-        return undefined
-      }
+const KeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
+  name: 'VaporKeepAlive',
+  __isKeepAlive: true,
+  props: {
+    include: [String, RegExp, Array],
+    exclude: [String, RegExp, Array],
+    max: [String, Number],
+  },
+  setup(props: KeepAliveProps, { slots }) {
+    if (!slots.default) {
+      return undefined
+    }
 
-      const keepAliveInstance = currentInstance! as KeepAliveInstance
-      const cache: Cache = new Map()
-      const keys: Keys = new Set()
-      const storageContainer = createElement('div')
-      const keptAliveScopes = new Map<any, EffectScope>()
-      let current: VaporComponentInstance | VaporFragment | undefined
+    const keepAliveInstance = currentInstance! as KeepAliveInstance
+    const cache: Cache = new Map()
+    const keys: Keys = new Set()
+    const storageContainer = createElement('div')
+    const keptAliveScopes = new Map<any, EffectScope>()
+    let current: VaporComponentInstance | VaporFragment | undefined
 
-      if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
-        ;(keepAliveInstance as any).__v_cache = cache
-      }
+    if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
+      ;(keepAliveInstance as any).__v_cache = cache
+    }
 
-      keepAliveInstance.getStorageContainer = () => storageContainer
+    keepAliveInstance.getStorageContainer = () => storageContainer
 
-      keepAliveInstance.getCachedComponent = comp => cache.get(comp)
+    keepAliveInstance.getCachedComponent = comp => cache.get(comp)
 
-      keepAliveInstance.activate = (instance, parentNode, anchor) => {
-        current = instance
-        activate(instance, parentNode, anchor)
-      }
+    keepAliveInstance.activate = (instance, parentNode, anchor) => {
+      current = instance
+      activate(instance, parentNode, anchor)
+    }
 
-      keepAliveInstance.deactivate = instance => {
-        current = undefined
-        deactivate(instance, storageContainer)
-      }
+    keepAliveInstance.deactivate = instance => {
+      current = undefined
+      deactivate(instance, storageContainer)
+    }
 
-      const innerCacheBlock = (
-        key: CacheKey,
-        instance: VaporComponentInstance | VaporFragment,
-      ) => {
-        const { max } = props
-
-        if (cache.has(key)) {
-          // make this key the freshest
-          keys.delete(key)
-          keys.add(key)
-        } else {
-          keys.add(key)
-          // prune oldest entry
-          if (max && keys.size > parseInt(max as string, 10)) {
-            pruneCacheEntry(keys.values().next().value!)
-          }
-        }
+    const innerCacheBlock = (
+      key: CacheKey,
+      instance: VaporComponentInstance | VaporFragment,
+    ) => {
+      const { max } = props
 
-        cache.set(key, instance)
-        current = instance
+      if (cache.has(key)) {
+        // make this key the freshest
+        keys.delete(key)
+        keys.add(key)
+      } else {
+        keys.add(key)
+        // prune oldest entry
+        if (max && keys.size > parseInt(max as string, 10)) {
+          pruneCacheEntry(keys.values().next().value!)
+        }
       }
 
-      const cacheBlock = () => {
-        // TODO suspense
-        const block = keepAliveInstance.block!
-        const [innerBlock, interop] = getInnerBlock(block)
-        if (!innerBlock || !shouldCache(innerBlock, props, interop)) return
-        innerCacheBlock(
-          interop ? innerBlock.vnode!.type : innerBlock.type,
-          innerBlock,
-        )
-      }
+      cache.set(key, instance)
+      current = instance
+    }
 
-      const processFragment = (frag: DynamicFragment) => {
-        const [innerBlock, interop] = getInnerBlock(frag.nodes)
-        if (!innerBlock || !shouldCache(innerBlock!, props, interop))
-          return false
+    const cacheBlock = () => {
+      // TODO suspense
+      const block = keepAliveInstance.block!
+      const [innerBlock, interop] = getInnerBlock(block)
+      if (!innerBlock || !shouldCache(innerBlock, props, interop)) return
+      innerCacheBlock(
+        interop ? innerBlock.vnode!.type : innerBlock.type,
+        innerBlock,
+      )
+    }
 
-        if (interop) {
-          if (cache.has(innerBlock.vnode!.type)) {
-            innerBlock.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
-          }
-          innerBlock.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
-        } else {
-          if (cache.has(innerBlock!.type)) {
-            innerBlock!.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
-          }
-          innerBlock!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
-        }
-        return true
-      }
+    const processFragment = (frag: DynamicFragment) => {
+      const [innerBlock, interop] = getInnerBlock(frag.nodes)
+      if (!innerBlock || !shouldCache(innerBlock!, props, interop)) return false
 
-      const cacheFragment = (fragment: DynamicFragment) => {
-        const [innerBlock, interop] = getInnerBlock(fragment.nodes)
-        if (!innerBlock || !shouldCache(innerBlock, props, interop)) return
-
-        let key: CacheKey
-        if (interop) {
-          innerBlock.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
-          key = innerBlock.vnode!.type
-        } else {
-          innerBlock.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
-          key = innerBlock.type
+      if (interop) {
+        if (cache.has(innerBlock.vnode!.type)) {
+          innerBlock.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
+        }
+        innerBlock.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
+      } else {
+        if (cache.has(innerBlock!.type)) {
+          innerBlock!.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
         }
-        innerCacheBlock(key, innerBlock)
+        innerBlock!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
       }
+      return true
+    }
 
-      const pruneCache = (filter: (name: string) => boolean) => {
-        cache.forEach((cached, key) => {
-          const instance = getInstanceFromCache(cached)
-          if (!instance) return
-          const name = getComponentName(instance.type)
-          if (name && !filter(name)) {
-            pruneCacheEntry(key)
-          }
-        })
+    const cacheFragment = (fragment: DynamicFragment) => {
+      const [innerBlock, interop] = getInnerBlock(fragment.nodes)
+      if (!innerBlock || !shouldCache(innerBlock, props, interop)) return
+
+      let key: CacheKey
+      if (interop) {
+        innerBlock.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
+        key = innerBlock.vnode!.type
+      } else {
+        innerBlock.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
+        key = innerBlock.type
       }
+      innerCacheBlock(key, innerBlock)
+    }
+
+    const pruneCache = (filter: (name: string) => boolean) => {
+      cache.forEach((cached, key) => {
+        const instance = getInstanceFromCache(cached)
+        if (!instance) return
+        const name = getComponentName(instance.type)
+        if (name && !filter(name)) {
+          pruneCacheEntry(key)
+        }
+      })
+    }
 
-      const pruneCacheEntry = (key: CacheKey) => {
-        const cached = cache.get(key)!
+    const pruneCacheEntry = (key: CacheKey) => {
+      const cached = cache.get(key)!
 
-        resetCachedShapeFlag(cached)
+      resetCachedShapeFlag(cached)
 
-        // don't unmount if the instance is the current one
-        if (cached !== current) {
-          remove(cached)
-        }
-        cache.delete(key)
-        keys.delete(key)
+      // don't unmount if the instance is the current one
+      if (cached !== current) {
+        remove(cached)
       }
+      cache.delete(key)
+      keys.delete(key)
+    }
 
-      // prune cache on include/exclude prop change
-      watch(
-        () => [props.include, props.exclude],
-        ([include, exclude]) => {
-          include && pruneCache(name => matches(include, name))
-          exclude && pruneCache(name => !matches(exclude, name))
-        },
-        // prune post-render after `current` has been updated
-        { flush: 'post', deep: true },
-      )
+    // prune cache on include/exclude prop change
+    watch(
+      () => [props.include, props.exclude],
+      ([include, exclude]) => {
+        include && pruneCache(name => matches(include, name))
+        exclude && pruneCache(name => !matches(exclude, name))
+      },
+      // prune post-render after `current` has been updated
+      { flush: 'post', deep: true },
+    )
+
+    onMounted(cacheBlock)
+    onUpdated(cacheBlock)
+    onBeforeUnmount(() => {
+      cache.forEach((cached, key) => {
+        const instance = getInstanceFromCache(cached)
+        if (!instance) return
 
-      onMounted(cacheBlock)
-      onUpdated(cacheBlock)
-      onBeforeUnmount(() => {
-        cache.forEach((cached, key) => {
-          const instance = getInstanceFromCache(cached)
-          if (!instance) return
-
-          resetCachedShapeFlag(cached)
-          cache.delete(key)
-
-          // current instance will be unmounted as part of keep-alive's unmount
-          if (current) {
-            const currentKey = isVaporComponent(current)
-              ? current.type
-              : current.vnode!.type
-            if (currentKey === key) {
-              // call deactivated hook
-              const da = instance.da
-              da && queuePostFlushCb(da)
-              return
-            }
+        resetCachedShapeFlag(cached)
+        cache.delete(key)
+
+        // current instance will be unmounted as part of keep-alive's unmount
+        if (current) {
+          const currentKey = isVaporComponent(current)
+            ? current.type
+            : current.vnode!.type
+          if (currentKey === key) {
+            // call deactivated hook
+            const da = instance.da
+            da && queuePostFlushCb(da)
+            return
           }
+        }
 
-          remove(cached, storageContainer)
-        })
-        keptAliveScopes.forEach(scope => scope.stop())
-        keptAliveScopes.clear()
+        remove(cached, storageContainer)
       })
-
-      let children = slots.default()
-      if (isArray(children)) {
-        children = children.filter(child => !(child instanceof Comment))
-        if (children.length > 1) {
-          if (__DEV__) {
-            warn(`KeepAlive should contain exactly one component child.`)
-          }
-          return children
+      keptAliveScopes.forEach(scope => scope.stop())
+      keptAliveScopes.clear()
+    })
+
+    let children = slots.default()
+    if (isArray(children)) {
+      children = children.filter(child => !(child instanceof Comment))
+      if (children.length > 1) {
+        if (__DEV__) {
+          warn(`KeepAlive should contain exactly one component child.`)
         }
+        return children
       }
+    }
 
-      // inject hooks to DynamicFragment to cache components during updates
-      const injectKeepAliveHooks = (frag: DynamicFragment) => {
-        ;(frag.onBeforeTeardown || (frag.onBeforeTeardown = [])).push(
-          (oldKey, nodes, scope) => {
-            // if the fragment's nodes include a component that should be cached
-            // return true to avoid tearing down the fragment's scope
-            if (processFragment(frag)) {
-              keptAliveScopes.set(oldKey, scope)
-              return true
-            }
-            return false
-          },
-        )
-        ;(frag.onBeforeMount || (frag.onBeforeMount = [])).push(() =>
-          cacheFragment(frag),
-        )
-        frag.getScope = key => {
-          const scope = keptAliveScopes.get(key)
-          if (scope) {
-            keptAliveScopes.delete(key)
-            return scope
+    // inject hooks to DynamicFragment to cache components during updates
+    const injectKeepAliveHooks = (frag: DynamicFragment) => {
+      ;(frag.onBeforeTeardown || (frag.onBeforeTeardown = [])).push(
+        (oldKey, nodes, scope) => {
+          // if the fragment's nodes include a component that should be cached
+          // return true to avoid tearing down the fragment's scope
+          if (processFragment(frag)) {
+            keptAliveScopes.set(oldKey, scope)
+            return true
           }
+          return false
+        },
+      )
+      ;(frag.onBeforeMount || (frag.onBeforeMount = [])).push(() =>
+        cacheFragment(frag),
+      )
+      frag.getScope = key => {
+        const scope = keptAliveScopes.get(key)
+        if (scope) {
+          keptAliveScopes.delete(key)
+          return scope
         }
       }
+    }
 
-      // process shapeFlag
-      if (isVaporComponent(children)) {
-        children.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
-        if (isAsyncWrapper(children)) {
-          injectKeepAliveHooks(children.block as DynamicFragment)
-        }
-      } else if (isInteropFragment(children)) {
-        children.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
-      } else if (isDynamicFragment(children)) {
-        processFragment(children)
-        injectKeepAliveHooks(children)
-        if (
-          isVaporComponent(children.nodes) &&
-          isAsyncWrapper(children.nodes)
-        ) {
-          injectKeepAliveHooks(children.nodes.block as DynamicFragment)
-        }
+    // process shapeFlag
+    if (isVaporComponent(children)) {
+      children.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
+      if (isAsyncWrapper(children)) {
+        injectKeepAliveHooks(children.block as DynamicFragment)
       }
+    } else if (isInteropFragment(children)) {
+      children.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
+    } else if (isDynamicFragment(children)) {
+      processFragment(children)
+      injectKeepAliveHooks(children)
+      if (isVaporComponent(children.nodes) && isAsyncWrapper(children.nodes)) {
+        injectKeepAliveHooks(children.nodes.block as DynamicFragment)
+      }
+    }
 
-      return children
-    },
-  })
+    return children
+  },
+})
+
+export const VaporKeepAliveImpl: ObjectVaporComponent =
+  /*@__PURE__*/ KeepAliveImpl
 
 const shouldCache = (
   block: GenericComponentInstance | VaporFragment,

+ 113 - 111
packages/runtime-vapor/src/components/TransitionGroup.ts

@@ -40,137 +40,139 @@ import { isFragment } from '../fragment'
 const positionMap = new WeakMap<TransitionBlock, DOMRect>()
 const newPositionMap = new WeakMap<TransitionBlock, DOMRect>()
 
-const decorate = (t: typeof VaporTransitionGroup) => {
+const decorate = <T extends ObjectVaporComponent>(t: T): T => {
   delete (t.props! as any).mode
   t.__vapor = true
   return t
 }
 
-export const VaporTransitionGroup: ObjectVaporComponent =
-  /*@__PURE__*/ decorate({
-    name: 'VaporTransitionGroup',
-
-    props: /*@__PURE__*/ extend({}, TransitionPropsValidators, {
-      tag: String,
-      moveClass: String,
-    }),
-
-    setup(props: TransitionGroupProps, { slots }) {
-      // Register transition hooks on first use
-      ensureTransitionHooksRegistered()
-
-      const instance = currentInstance as VaporComponentInstance
-      const state = useTransitionState()
-
-      // use proxy to keep props reference stable
-      let cssTransitionProps = resolveTransitionProps(props)
-      const propsProxy = new Proxy({} as typeof cssTransitionProps, {
-        get(_, key) {
-          return cssTransitionProps[key as keyof typeof cssTransitionProps]
-        },
-      })
+const VaporTransitionGroupImpl: ObjectVaporComponent = {
+  name: 'VaporTransitionGroup',
 
-      renderEffect(() => {
-        cssTransitionProps = resolveTransitionProps(props)
-      })
+  props: /*@__PURE__*/ extend({}, TransitionPropsValidators, {
+    tag: String,
+    moveClass: String,
+  }),
 
-      let prevChildren: TransitionBlock[]
-      let children: TransitionBlock[]
-      const slottedBlock = slots.default && slots.default()
+  setup(props: TransitionGroupProps, { slots }) {
+    // Register transition hooks on first use
+    ensureTransitionHooksRegistered()
 
-      onBeforeUpdate(() => {
-        prevChildren = []
-        children = getTransitionBlocks(slottedBlock)
-        if (children) {
-          for (let i = 0; i < children.length; i++) {
-            const child = children[i]
-            if (isValidTransitionBlock(child)) {
-              prevChildren.push(child)
-              // disabled transition during enter, so the children will be
-              // inserted into the correct position immediately. this prevents
-              // `recordPosition` from getting incorrect positions in `onUpdated`
-              child.$transition!.disabled = true
-              positionMap.set(
-                child,
-                getTransitionElement(child).getBoundingClientRect(),
-              )
-            }
-          }
-        }
-      })
+    const instance = currentInstance as VaporComponentInstance
+    const state = useTransitionState()
 
-      onUpdated(() => {
-        if (!prevChildren.length) {
-          return
-        }
-        const moveClass = props.moveClass || `${props.name || 'v'}-move`
-        const firstChild = getFirstConnectedChild(prevChildren)
-        if (
-          !firstChild ||
-          !hasCSSTransform(
-            firstChild as ElementWithTransition,
-            firstChild.parentNode as Node,
-            moveClass,
-          )
-        ) {
-          prevChildren = []
-          return
-        }
+    // use proxy to keep props reference stable
+    let cssTransitionProps = resolveTransitionProps(props)
+    const propsProxy = new Proxy({} as typeof cssTransitionProps, {
+      get(_, key) {
+        return cssTransitionProps[key as keyof typeof cssTransitionProps]
+      },
+    })
 
-        prevChildren.forEach(callPendingCbs)
-        prevChildren.forEach(child => {
-          child.$transition!.disabled = false
-          recordPosition(child)
-        })
-        const movedChildren = prevChildren.filter(applyTranslation)
-
-        // force reflow to put everything in position
-        forceReflow()
-
-        movedChildren.forEach(c =>
-          handleMovedChildren(
-            getTransitionElement(c) as ElementWithTransition,
-            moveClass,
-          ),
-        )
-        prevChildren = []
-      })
+    renderEffect(() => {
+      cssTransitionProps = resolveTransitionProps(props)
+    })
 
-      // store props and state on fragment for reusing during insert new items
-      setTransitionHooksOnFragment(slottedBlock, {
-        props: propsProxy,
-        state,
-        instance,
-      } as VaporTransitionHooks)
+    let prevChildren: TransitionBlock[]
+    let children: TransitionBlock[]
+    const slottedBlock = slots.default && slots.default()
 
+    onBeforeUpdate(() => {
+      prevChildren = []
       children = getTransitionBlocks(slottedBlock)
-      for (let i = 0; i < children.length; i++) {
-        const child = children[i]
-        if (isValidTransitionBlock(child)) {
-          if (child.$key != null) {
-            const hooks = resolveTransitionHooks(
+      if (children) {
+        for (let i = 0; i < children.length; i++) {
+          const child = children[i]
+          if (isValidTransitionBlock(child)) {
+            prevChildren.push(child)
+            // disabled transition during enter, so the children will be
+            // inserted into the correct position immediately. this prevents
+            // `recordPosition` from getting incorrect positions in `onUpdated`
+            child.$transition!.disabled = true
+            positionMap.set(
               child,
-              propsProxy,
-              state,
-              instance!,
+              getTransitionElement(child).getBoundingClientRect(),
             )
-            setTransitionHooks(child, hooks)
-          } else if (__DEV__) {
-            warn(`<transition-group> children must be keyed`)
           }
         }
       }
+    })
+
+    onUpdated(() => {
+      if (!prevChildren.length) {
+        return
+      }
+      const moveClass = props.moveClass || `${props.name || 'v'}-move`
+      const firstChild = getFirstConnectedChild(prevChildren)
+      if (
+        !firstChild ||
+        !hasCSSTransform(
+          firstChild as ElementWithTransition,
+          firstChild.parentNode as Node,
+          moveClass,
+        )
+      ) {
+        prevChildren = []
+        return
+      }
 
-      const tag = props.tag
-      if (tag) {
-        const container = createElement(tag)
-        insert(slottedBlock, container)
-        return container
-      } else {
-        return slottedBlock
+      prevChildren.forEach(callPendingCbs)
+      prevChildren.forEach(child => {
+        child.$transition!.disabled = false
+        recordPosition(child)
+      })
+      const movedChildren = prevChildren.filter(applyTranslation)
+
+      // force reflow to put everything in position
+      forceReflow()
+
+      movedChildren.forEach(c =>
+        handleMovedChildren(
+          getTransitionElement(c) as ElementWithTransition,
+          moveClass,
+        ),
+      )
+      prevChildren = []
+    })
+
+    // store props and state on fragment for reusing during insert new items
+    setTransitionHooksOnFragment(slottedBlock, {
+      props: propsProxy,
+      state,
+      instance,
+    } as VaporTransitionHooks)
+
+    children = getTransitionBlocks(slottedBlock)
+    for (let i = 0; i < children.length; i++) {
+      const child = children[i]
+      if (isValidTransitionBlock(child)) {
+        if (child.$key != null) {
+          const hooks = resolveTransitionHooks(
+            child,
+            propsProxy,
+            state,
+            instance!,
+          )
+          setTransitionHooks(child, hooks)
+        } else if (__DEV__) {
+          warn(`<transition-group> children must be keyed`)
+        }
       }
-    },
-  })
+    }
+
+    const tag = props.tag
+    if (tag) {
+      const container = createElement(tag)
+      insert(slottedBlock, container)
+      return container
+    } else {
+      return slottedBlock
+    }
+  },
+}
+
+export const VaporTransitionGroup: ObjectVaporComponent =
+  /*@__PURE__*/ decorate(VaporTransitionGroupImpl)
 
 function getTransitionBlocks(block: Block) {
   let children: TransitionBlock[] = []