text: /* @flow */
  
  import { isRegExp, remove, isDef } from 'shared/util'
  import { getFirstComponentChild } from 'core/vdom/helpers/index'
  
  type CacheEntry = {
    name: ?string;
    tag: ?string;
    componentInstance: Component;
  };
  
  type CacheEntryMap = { [key: string]: ?CacheEntry };
  
  function getComponentName (opts: ?VNodeComponentOptions): ?string {
    return opts && (opts.Ctor.options.name || opts.tag)
  }
  
  function matches (pattern: string | RegExp | Array<string>, name: string): boolean {
    if (Array.isArray(pattern)) {
      return pattern.indexOf(name) > -1
    } else if (typeof pattern === 'string') {
      return pattern.split(',').indexOf(name) > -1
    } else if (isRegExp(pattern)) {
      return pattern.test(name)
    }
    /* istanbul ignore next */
    return false
  }
  
  function pruneCache (keepAliveInstance: any, filter: Function) {
    const { cache, keys, _vnode } = keepAliveInstance
    for (const key in cache) {
      const entry: ?CacheEntry = cache[key]
      if (entry) {
        const name: ?string = entry.name
        if (name && !filter(name)) {
          pruneCacheEntry(cache, key, keys, _vnode)
        }
      }
    }
  }
  
  function pruneCacheEntry (
    cache: CacheEntryMap,
    key: string,
    keys: Array<string>,
    current?: VNode
  ) {
    const entry: ?CacheEntry = cache[key]
    if (entry && (!current || entry.tag !== current.tag)) {
      entry.componentInstance.$destroy()
    }
    cache[key] = null
    remove(keys, key)
  }
  
  const patternTypes: Array<Function> = [String, RegExp, Array]
  
  export default {
    name: 'keep-alive',
    abstract: true,
  
    props: {
      include: patternTypes,
      exclude: patternTypes,
      max: [String, Number]
    },
  
    methods: {
      cacheVNode() {
        const { cache, keys, vnodeToCache, keyToCache } = this
        if (vnodeToCache) {
          const { tag, componentInstance, componentOptions } = vnodeToCache
          cache[keyToCache] = {
            name: getComponentName(componentOptions),
            tag,
            componentInstance,
          }
          keys.push(keyToCache)
          // prune oldest entry
          if (isDef(this.max) && keys.length > parseInt(this.max)) {
            pruneCacheEntry(cache, keys[0], keys, this._vnode)
          }
          this.vnodeToCache = null
        }
      }
    },
  
    created () {
      this.cache = Object.create(null)
      this.keys = []
    },
  
    destroyed () {
      for (const key in this.cache) {
        pruneCacheEntry(this.cache, key, this.keys)
      }
    },
  
    mounted () {
      this.cacheVNode()
      this.$watch('include', val => {
        pruneCache(this, name => matches(val, name))
      })
      this.$watch('exclude', val => {
        pruneCache(this, name => !matches(val, name))
      })
    },
  
    updated () {
      this.cacheVNode()
    },
  
    render () {
      const slot = this.$slots.default
      const vnode: VNode = getFirstComponentChild(slot)
      const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
      if (componentOptions) {
        // check pattern
        const name: ?string = getComponentName(componentOptions)
        const { include, exclude } = this
        if (
          // not included
          (include && (!name || !matches(include, name))) ||
          // excluded
          (exclude && name && matches(exclude, name))
        ) {
          return vnode
        }
  
        const { cache, keys } = this
        const key: ?string = vnode.key == null
          // same constructor may get registered as different local components
          // so cid alone is not enough (#3269)
          ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
          : vnode.key
        if (cache[key]) {
          vnode.componentInstance = cache[key].componentInstance
          // make current key freshest
          remove(keys, key)
          keys.push(key)
        } else {
          // delay setting the cache until update
          this.vnodeToCache = vnode
          this.keyToCache = key
        }
  
        vnode.data.keepAlive = true
      }
      return vnode || (slot && slot[0])
    }
  }
  