]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(hmr): prevent update unmounting component during HMR reload (#13815)
authoredison <daiwei521@126.com>
Tue, 2 Sep 2025 09:07:36 +0000 (17:07 +0800)
committerGitHub <noreply@github.com>
Tue, 2 Sep 2025 09:07:36 +0000 (17:07 +0800)
close vitejs/vite-plugin-vue#599

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

index 01feb91aceed7f31eb8553a5684c8344ab3cda62..e8e0398e174951e13a4bd10d236e6771333ecce5 100644 (file)
@@ -937,4 +937,55 @@ describe('hot module replacement', () => {
     rerender(id, () => 'bar')
     expect(serializeInner(root)).toBe('bar')
   })
+
+  // https://github.com/vitejs/vite-plugin-vue/issues/599
+  // Both Outer and Inner are reloaded when './server.js' changes
+  test('reload nested components from single update', async () => {
+    const innerId = 'nested-reload-inner'
+    const outerId = 'nested-reload-outer'
+
+    let Inner = {
+      __hmrId: innerId,
+      render() {
+        return h('div', 'foo')
+      },
+    }
+    let Outer = {
+      __hmrId: outerId,
+      render() {
+        return h(Inner)
+      },
+    }
+
+    createRecord(innerId, Inner)
+    createRecord(outerId, Outer)
+
+    const App = {
+      render: () => h(Outer),
+    }
+
+    const root = nodeOps.createElement('div')
+    render(h(App), root)
+    expect(serializeInner(root)).toBe('<div>foo</div>')
+
+    Inner = {
+      __hmrId: innerId,
+      render() {
+        return h('div', 'bar')
+      },
+    }
+    Outer = {
+      __hmrId: outerId,
+      render() {
+        return h(Inner)
+      },
+    }
+
+    // trigger reload for both Outer and Inner
+    reload(outerId, Outer)
+    reload(innerId, Inner)
+    await nextTick()
+
+    expect(serializeInner(root)).toBe('<div>bar</div>')
+  })
 })
index 2c46ea73b502515f3f2678326759aa5a8f63d3ad..4191a34f82f32e66ab08e379534a18e2cb4fff57 100644 (file)
@@ -147,11 +147,15 @@ function reload(id: string, newComp: HMRComponent): void {
       // 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(() => {
-        isHmrUpdating = true
-        instance.parent!.update()
-        isHmrUpdating = false
-        // #6930, #11248 avoid infinite recursion
-        dirtyInstances.delete(instance)
+        // vite-plugin-vue/issues/599
+        // don't update if the job is already disposed
+        if (!(instance.job.flags! & SchedulerJobFlags.DISPOSED)) {
+          isHmrUpdating = true
+          instance.parent!.update()
+          isHmrUpdating = false
+          // #6930, #11248 avoid infinite recursion
+          dirtyInstances.delete(instance)
+        }
       })
     } else if (instance.appContext.reload) {
       // root instance mounted via createApp() has a reload method