-
Notifications
You must be signed in to change notification settings - Fork 916
Hot reload doesn't drop the old component if it's wrapped in <keep-alive> #700
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
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 |
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: As mentioned on the other thread, I'm still wondering if it might be easier just to always append the value of |
It's a shame this issue still exists. I've just run into the bug as well. |
(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.
The text was updated successfully, but these errors were encountered: