]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(hmr): avoid infinite recursion when reloading hmr components (#6936)
authorZeke Zhang <958414905@qq.com>
Fri, 31 May 2024 15:41:13 +0000 (23:41 +0800)
committerGitHub <noreply@github.com>
Fri, 31 May 2024 15:41:13 +0000 (23:41 +0800)
close #6930

packages/runtime-core/__tests__/hmr.spec.ts
packages/runtime-core/src/hmr.ts

index 619147d55c173e2f0c113f7b5024562d21e78a70..39aece16a5a02364ff220fe8ff0f599f250731ee 100644 (file)
@@ -6,6 +6,7 @@ import {
   h,
   nextTick,
   nodeOps,
+  ref,
   render,
   serializeInner,
   triggerEvent,
@@ -415,6 +416,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 = vi.fn()
+    const mountSpy = vi.fn()
+
+    const Child: ComponentOptions = {
+      __hmrId: childId,
+      data() {
+        return { count: 0 }
+      },
+      expose: ['count'],
+      unmounted: unmountSpy,
+      render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
+    }
+    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(`<div>0</div>0`)
+
+    reload(childId, {
+      __hmrId: childId,
+      data() {
+        return { count: 1 }
+      },
+      mounted: mountSpy,
+      render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
+    })
+    await nextTick()
+    expect(serializeInner(root)).toBe(`<div>1</div>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 () => {
index b5904a4fc5baf40cbbeb95d57f1a8d3b8c6b93f7..8196eb891953d9c4ea6380d47e552d28cbdabdf7 100644 (file)
@@ -139,7 +139,11 @@ function reload(id: string, newComp: HMRComponent) {
       // 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.
       instance.parent.effect.dirty = true
-      queueJob(instance.parent.update)
+      queueJob(() => {
+        instance.parent!.update()
+        // #6930 avoid infinite recursion
+        hmrDirtyComponents.delete(oldComp)
+      })
     } else if (instance.appContext.reload) {
       // root instance mounted via createApp() has a reload method
       instance.appContext.reload()