From 5392bfd98dee230133ce88baa0d515cef18afe98 Mon Sep 17 00:00:00 2001 From: Rairn <958414905@qq.com> Date: Mon, 24 Oct 2022 17:55:14 +0800 Subject: [PATCH] fix(hmr): avoid infinite recursion when reloading hmr components (#6930) --- packages/runtime-core/__tests__/hmr.spec.ts | 50 ++++++++++++++++++++- packages/runtime-core/src/hmr.ts | 8 +++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/__tests__/hmr.spec.ts b/packages/runtime-core/__tests__/hmr.spec.ts index eaef8d401a7..b75d5171597 100644 --- a/packages/runtime-core/__tests__/hmr.spec.ts +++ b/packages/runtime-core/__tests__/hmr.spec.ts @@ -8,7 +8,8 @@ import { serializeInner, triggerEvent, TestElement, - nextTick + nextTick, + ref } from '@vue/runtime-test' import * as runtimeTest from '@vue/runtime-test' import { registerRuntimeCompiler, createApp } from '@vue/runtime-test' @@ -194,6 +195,53 @@ describe('hot module replacement', () => { expect(mountSpy).toHaveBeenCalledTimes(1) }) + // #6930 + test('reload: avoid infinite recursion', async () => { + const root = nodeOps.createElement('div') + const childId = 'test-child-6930' + const unmountSpy = jest.fn() + const mountSpy = jest.fn() + + const Child: ComponentOptions = { + __hmrId: childId, + data() { + return { count: 0 } + }, + expose: ['count'], + unmounted: unmountSpy, + render: compileToFunction(`
{{ count }}
`) + } + createRecord(childId, Child) + + const Parent: ComponentOptions = { + setup() { + const com = ref() + const changeRef = (value: any) => { + com.value = value + } + + return () => [h(Child, {ref: changeRef}), com.value?.count] + } + } + + render(h(Parent), root) + await nextTick() + expect(serializeInner(root)).toBe(`
0
0`) + + reload(childId, { + __hmrId: childId, + data() { + return { count: 1 } + }, + mounted: mountSpy, + render: compileToFunction(`
{{ count }}
`) + }) + await nextTick() + expect(serializeInner(root)).toBe(`
1
1`) + expect(unmountSpy).toHaveBeenCalledTimes(1) + expect(mountSpy).toHaveBeenCalledTimes(1) + }) + // #1156 - static nodes should retain DOM element reference across updates // when HMR is active test('static el reference', async () => { diff --git a/packages/runtime-core/src/hmr.ts b/packages/runtime-core/src/hmr.ts index 3c3f5208bcc..beab6582510 100644 --- a/packages/runtime-core/src/hmr.ts +++ b/packages/runtime-core/src/hmr.ts @@ -135,7 +135,13 @@ function reload(id: string, newComp: HMRComponent) { // 4. Force the parent instance to re-render. This will cause all updated // components to be unmounted and re-mounted. Queue the update so that we // don't end up forcing the same parent to re-render multiple times. - queueJob(instance.parent.update) + queueJob(() => { + if (instance.parent) { + instance.parent.update() + // #6930 avoid infinite recursion + hmrDirtyComponents.delete(oldComp) + } + }) // instance is the inner component of an async custom element // invoke to reset styles if (