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 (