1. /* @flow */
    2. import { isRegExp, remove } from 'shared/util'
    3. import { getFirstComponentChild } from 'core/vdom/helpers/index'
    4. type VNodeCache = { [key: string]: ?VNode };
    5. function getComponentName (opts: ?VNodeComponentOptions): ?string {
    6. return opts && (opts.Ctor.options.name || opts.tag)
    7. }
    8. function matches (pattern: string | RegExp | Array<string>, name: string): boolean {
    9. if (Array.isArray(pattern)) {
    10. return pattern.indexOf(name) > -1
    11. } else if (typeof pattern === 'string') {
    12. return pattern.split(',').indexOf(name) > -1
    13. } else if (isRegExp(pattern)) {
    14. return pattern.test(name)
    15. }
    16. /* istanbul ignore next */
    17. return false
    18. }
    19. function pruneCache (keepAliveInstance: any, filter: Function) {
    20. const { cache, keys, _vnode } = keepAliveInstance
    21. for (const key in cache) {
    22. const cachedNode: ?VNode = cache[key]
    23. if (cachedNode) {
    24. const name: ?string = getComponentName(cachedNode.componentOptions)
    25. if (name && !filter(name)) {
    26. pruneCacheEntry(cache, key, keys, _vnode)
    27. }
    28. }
    29. }
    30. }
    31. function pruneCacheEntry (
    32. cache: VNodeCache,
    33. key: string,
    34. keys: Array<string>,
    35. current?: VNode
    36. ) {
    37. const cached = cache[key]
    38. if (cached && (!current || cached.tag !== current.tag)) {
    39. cached.componentInstance.$destroy()
    40. }
    41. cache[key] = null
    42. remove(keys, key)
    43. }
    44. const patternTypes: Array<Function> = [String, RegExp, Array]
    45. export default {
    46. name: 'keep-alive',
    47. abstract: true,
    48. props: {
    49. include: patternTypes,
    50. exclude: patternTypes,
    51. max: [String, Number]
    52. },
    53. created () {
    54. this.cache = Object.create(null)
    55. this.keys = []
    56. },
    57. destroyed () {
    58. for (const key in this.cache) {
    59. pruneCacheEntry(this.cache, key, this.keys)
    60. }
    61. },
    62. mounted () {
    63. this.$watch('include', val => {
    64. pruneCache(this, name => matches(val, name))
    65. })
    66. this.$watch('exclude', val => {
    67. pruneCache(this, name => !matches(val, name))
    68. })
    69. },
    70. render () {
    71. const slot = this.$slots.default
    72. const vnode: VNode = getFirstComponentChild(slot)
    73. const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    74. if (componentOptions) {
    75. // check pattern
    76. const name: ?string = getComponentName(componentOptions)
    77. const { include, exclude } = this
    78. if (
    79. // not included
    80. (include && (!name || !matches(include, name))) ||
    81. // excluded
    82. (exclude && name && matches(exclude, name))
    83. ) {
    84. return vnode
    85. }
    86. const { cache, keys } = this
    87. const key: ?string = vnode.key == null
    88. // same constructor may get registered as different local components
    89. // so cid alone is not enough (#3269)
    90. ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
    91. : vnode.key
    92. if (cache[key]) {
    93. vnode.componentInstance = cache[key].componentInstance
    94. // make current key freshest
    95. remove(keys, key)
    96. keys.push(key)
    97. } else {
    98. cache[key] = vnode
    99. keys.push(key)
    100. // prune oldest entry
    101. if (this.max && keys.length > parseInt(this.max)) {
    102. pruneCacheEntry(cache, keys[0], keys, this._vnode)
    103. }
    104. }
    105. vnode.data.keepAlive = true
    106. }
    107. return vnode || (slot && slot[0])
    108. }
    109. }