瀏覽代碼

fix(hmr): prevent update unmounting component during HMR reload (#13815)

close vitejs/vite-plugin-vue#599
edison 7 月之前
父節點
當前提交
ef20b86b36
共有 2 個文件被更改,包括 60 次插入5 次删除
  1. 51 0
      packages/runtime-core/__tests__/hmr.spec.ts
  2. 9 5
      packages/runtime-core/src/hmr.ts

+ 51 - 0
packages/runtime-core/__tests__/hmr.spec.ts

@@ -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>')
+  })
 })

+ 9 - 5
packages/runtime-core/src/hmr.ts

@@ -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