Open pevzi opened 7 years ago
Would you mind creating a small reproduction so we can be sure the behaviour can be replicated in isolation?
Thanks!
Here is a small project based on webpack-simple
template. Just do npm run dev
and try to change the msg
field in src/Foo.vue
.
Thanks!
for vue 2.6, i did some work around, this will require name to be set in component:
import Vue from "vue"
/*
* https://github.com/vuejs/vue-loader/issues/1332#issuecomment-601572625
*/
function isDef(v) {
return v !== undefined && v !== null
}
function isAsyncPlaceholder(node) {
return node.isComment && node.asyncFactory
}
function getFirstComponentChild(children) {
if (Array.isArray(children)) {
for (var i = 0; i < children.length; i++) {
var c = children[i]
if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
return c
}
}
}
}
function getComponentName(opts) {
return opts && (opts.Ctor.options.name || opts.tag)
}
function matches(pattern, name) {
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 remove(arr, item) {
if (arr.length) {
var index = arr.indexOf(item)
if (index > -1) {
return arr.splice(index, 1)
}
}
}
function pruneCacheEntry(cache, key, keys, current) {
var cached$$1 = cache[key]
if (cached$$1 && (!current || cached$$1.tag !== current.tag)) {
cached$$1.componentInstance.$destroy()
}
cache[key] = null
remove(keys, key)
}
function pruneCache(keepAliveInstance, filter) {
var cache = keepAliveInstance.cache
var keys = keepAliveInstance.keys
var _vnode = keepAliveInstance._vnode
const cachedNameKeyMap = keepAliveInstance.cachedNameKeyMap
for (var key in cache) {
var cachedNode = cache[key]
if (cachedNode) {
var name = getComponentName(cachedNode.componentOptions)
if (name && !filter(name)) {
delete cachedNameKeyMap[name]
pruneCacheEntry(cache, key, keys, _vnode)
}
}
}
}
const patternTypes = [String, RegExp, Array]
const KeepAlive = {
name: "keep-alive",
abstract: true,
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number],
},
created() {
this.cache = Object.create(null)
this.cachedNameKeyMap = Object.create(null)
this.keys = []
},
destroyed() {
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted() {
this.$watch("include", val => {
pruneCache(this, name => matches(val, name))
})
this.$watch("exclude", val => {
pruneCache(this, name => !matches(val, name))
})
},
render() {
const slot = this.$slots.default
const vnode = getFirstComponentChild(slot)
const componentOptions = vnode && vnode.componentOptions
if (componentOptions) {
// check pattern
const name = getComponentName(componentOptions)
const { include, exclude } = this
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode
}
const { cache, cachedNameKeyMap, keys } = this
const key =
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 {
cache[key] = vnode
keys.push(key)
// prune old component for hmr
if (name && cachedNameKeyMap[name] && cachedNameKeyMap[name] !== key) {
pruneCacheEntry(cache, cachedNameKeyMap[name], keys)
}
cachedNameKeyMap[name] = key
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
},
}
// ovveride original keep-alive
process.env.NODE_ENV === "development" && Vue.component("KeepAlive", KeepAlive)
@ericwu-wish thanks for sharing the example! Being able to work around the HMR problem without disabling keep-alive entirely in dev would definitely shore up a ton of confusing code we've had to write inside components to determine if keep-alive is being used or not since we have different behaviors in certain cases within created/destroyed (e.g. maybe we only refresh a small amount of data when the component is activated).
Are you still using this? I tried implementing it and have confirmed that the code is running after some debugging but my components still disappear when HMR runs.
I took over this project which was setup using Laravel Mix so I suspect I need to dig into the way it's configuring webpack and the versions it's using but figured I'd verify that I should expect HMR to not wipe out my components on reload when using your overridden version of keep-alive here.
Any advice?
I posted this in #1332 but wanted to share here.
I ended up rolling the comments from @nailfar & @ericwu-wish into a plugin: https://www.npmjs.com/package/vue-keep-alive-dev
As mentioned on the other thread, I'm still wondering if it might be easier just to always append the value of componentOptions.Ctor.cid
to the cache key. This is a lot of extra code for what is ultimately a small modification to a single line which doesn't have any negative affect in production.
It's a shame this issue still exists. I've just run into the bug as well. Do we know if this will be fixed/addressed in Vue 3?
(not sure if I've chosen the appropriate bug tracker for this)
Steps to reproduce:
webpack
template.App.vue
and wrap the<hello>
component in a<keep-alive>
.Hello
component (modifying the template doesn't trigger the issue).Expected behavior: the wrapped component is replaced with the new version. Observed behavior: the old version is kept alive, and each change results in an additional inactive component.