From b03908f23408602cdc69bb4bc5bddcaa4e4d1129 Mon Sep 17 00:00:00 2001 From: Math-chen Date: Fri, 24 Dec 2021 16:24:27 +0800 Subject: [PATCH] fix bug #5073 HMR on keepalive components caused error --- packages/runtime-core/__tests__/hmr.spec.ts | 57 +++++++++++++++++++ .../runtime-core/src/components/KeepAlive.ts | 9 +++ packages/runtime-core/src/hmr.ts | 7 +++ 3 files changed, 73 insertions(+) diff --git a/packages/runtime-core/__tests__/hmr.spec.ts b/packages/runtime-core/__tests__/hmr.spec.ts index eaef8d401a7..060d3ec9a50 100644 --- a/packages/runtime-core/__tests__/hmr.spec.ts +++ b/packages/runtime-core/__tests__/hmr.spec.ts @@ -469,4 +469,61 @@ describe('hot module replacement', () => { render(h(Foo), root) expect(serializeInner(root)).toBe('bar') }) + + test('reload for keep-alive instance',async () => { + const parentId = 'keep-alive-parent-reload' + const barId = 'keep-alive-bar-reload' + const fooId = 'keep-alive-foo-reload' + const root = nodeOps.createElement('div') + const Bar: ComponentOptions = { + __hmrId: barId, + render: compileToFunction(`
bar
`) + } + const Foo: ComponentOptions = { + __hmrId: fooId, + render: compileToFunction(`
foo
`) + } + const Parent: ComponentOptions = { + __hmrId: parentId, + data() { + return { + value: 'bar' + } + }, + components: { + foo: Foo, + bar: Bar + }, + render: compileToFunction(` + + + + + `) + } + createRecord(parentId, Parent) + createRecord(barId, Bar) + createRecord(fooId, Foo) + const app = createApp(Parent) + app.mount(root) + expect(serializeInner(root)).toBe('
bar
') + reload(barId, { + __hmrId: barId, + render: compileToFunction(`
bar1
`) + }) + await nextTick() + expect(serializeInner(root)).toBe('
bar1
') + triggerEvent(root.children[1] as TestElement, 'click') + await nextTick() + expect(serializeInner(root)).toBe('
foo
') + triggerEvent(root.children[1] as TestElement, 'click') + await nextTick() + expect(serializeInner(root)).toBe('
bar1
') + reload(barId, { + __hmrId: barId, + render: compileToFunction(`
bar2
`) + }) + await nextTick() + expect(serializeInner(root)).toBe('
bar2
') + }) }) diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index c6180019f86..5970c5f6e87 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -42,6 +42,7 @@ import { setTransitionHooks } from './BaseTransition' import { ComponentRenderContext } from '../componentPublicInstance' import { devtoolsComponentAdded } from '../devtools' import { isAsyncWrapper } from '../apiAsyncComponent' +import { registerHMR, unregisterHMR } from '../hmr' type MatchPattern = string | RegExp | string[] | RegExp[] @@ -121,6 +122,9 @@ const KeepAliveImpl: ComponentOptions = { sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => { const instance = vnode.component! + if(__DEV__ && instance.type.__hmrId) { + registerHMR(instance); + } move(vnode, container, anchor, MoveType.ENTER, parentSuspense) // in case props have changed patch( @@ -153,6 +157,9 @@ const KeepAliveImpl: ComponentOptions = { sharedContext.deactivate = (vnode: VNode) => { const instance = vnode.component! + if(__DEV__ && instance.type.__hmrId) { + unregisterHMR(instance); + } move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense) queuePostRenderEffect(() => { if (instance.da) { @@ -171,6 +178,8 @@ const KeepAliveImpl: ComponentOptions = { } } + sharedContext.pruneCacheEntry = pruneCacheEntry + function unmount(vnode: VNode) { // reset the shapeFlag so it can be properly unmounted resetShapeFlag(vnode) diff --git a/packages/runtime-core/src/hmr.ts b/packages/runtime-core/src/hmr.ts index 3c3f5208bcc..1bc7c919015 100644 --- a/packages/runtime-core/src/hmr.ts +++ b/packages/runtime-core/src/hmr.ts @@ -112,6 +112,13 @@ function reload(id: string, newComp: HMRComponent) { for (const instance of instances) { const oldComp = normalizeClassComponent(instance.type as HMRComponent) + //#5073 # need to cleanup the cache in keep-alive and reset the shapeFlag + if(instance.parent && (instance.parent.type as ComponentOptions).__isKeepAlive) { + const vnode = instance.vnode + const key = vnode.key == null ? vnode.type : vnode.key; + (instance.parent.ctx as any).pruneCacheEntry(key) + } + if (!hmrDirtyComponents.has(oldComp)) { // 1. Update existing comp definition to match new one if (oldComp !== record.initialDef) {