Explorar o código

fix(hmr): force full update in child component on slot update

Evan You %!s(int64=6) %!d(string=hai) anos
pai
achega
2408a65662

+ 7 - 7
packages/runtime-core/__tests__/hmr.spec.ts

@@ -45,7 +45,7 @@ describe('hot module replacement', () => {
 
     const Child: ComponentOptions = {
       __hmrId: childId,
-      render: compileToFunction(`<slot/>`)
+      render: compileToFunction(`<div><slot/></div>`)
     }
     createRecord(childId, Child)
 
@@ -62,13 +62,13 @@ describe('hot module replacement', () => {
     createRecord(parentId, Parent)
 
     render(h(Parent), root)
-    expect(serializeInner(root)).toBe(`<div>00</div>`)
+    expect(serializeInner(root)).toBe(`<div>0<div>0</div></div>`)
 
     // Perform some state change. This change should be preserved after the
     // re-render!
     triggerEvent(root.children[0] as TestElement, 'click')
     await nextTick()
-    expect(serializeInner(root)).toBe(`<div>11</div>`)
+    expect(serializeInner(root)).toBe(`<div>1<div>1</div></div>`)
 
     // // Update text while preserving state
     rerender(
@@ -77,7 +77,7 @@ describe('hot module replacement', () => {
         `<div @click="count++">{{ count }}!<Child>{{ count }}</Child></div>`
       )
     )
-    expect(serializeInner(root)).toBe(`<div>1!1</div>`)
+    expect(serializeInner(root)).toBe(`<div>1!<div>1</div></div>`)
 
     // Should force child update on slot content change
     rerender(
@@ -86,7 +86,7 @@ describe('hot module replacement', () => {
         `<div @click="count++">{{ count }}!<Child>{{ count }}!</Child></div>`
       )
     )
-    expect(serializeInner(root)).toBe(`<div>1!1!</div>`)
+    expect(serializeInner(root)).toBe(`<div>1!<div>1!</div></div>`)
 
     // Should force update element children despite block optimization
     rerender(
@@ -97,7 +97,7 @@ describe('hot module replacement', () => {
       </div>`
       )
     )
-    expect(serializeInner(root)).toBe(`<div>1<span>1</span>1!</div>`)
+    expect(serializeInner(root)).toBe(`<div>1<span>1</span><div>1!</div></div>`)
 
     // Should force update child slot elements
     rerender(
@@ -108,7 +108,7 @@ describe('hot module replacement', () => {
       </div>`
       )
     )
-    expect(serializeInner(root)).toBe(`<div><span>1</span></div>`)
+    expect(serializeInner(root)).toBe(`<div><div><span>1</span></div></div>`)
   })
 
   test('reload', async () => {

+ 1 - 1
packages/runtime-core/src/component.ts

@@ -313,7 +313,7 @@ export interface ComponentInternalInstance {
    * hmr marker (dev only)
    * @internal
    */
-  renderUpdated?: boolean
+  hmrUpdated?: boolean
 }
 
 const emptyAppContext = createAppContext()

+ 1 - 1
packages/runtime-core/src/componentRenderUtils.ts

@@ -251,7 +251,7 @@ export function shouldUpdateComponent(
     __DEV__ &&
     (prevChildren || nextChildren) &&
     parentComponent &&
-    parentComponent.renderUpdated
+    parentComponent.hmrUpdated
   ) {
     return true
   }

+ 11 - 4
packages/runtime-core/src/componentSlots.ts

@@ -18,6 +18,7 @@ import {
 import { warn } from './warning'
 import { isKeepAlive } from './components/KeepAlive'
 import { withCtx } from './helpers/withRenderContext'
+import { queuePostFlushCb } from './scheduler'
 
 export type Slot = (...args: any[]) => VNode[]
 
@@ -124,11 +125,17 @@ export const updateSlots = (
   if (vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
     if ((children as RawSlots)._ === 1) {
       // compiled slots.
-      if (
+      if (__DEV__ && instance.parent && instance.parent.hmrUpdated) {
+        // Parent was HMR updated so slot content may have changed.
+        // force update slots and mark instance for hmr as well
+        extend(slots, children as Slots)
+        instance.hmrUpdated = true
+        queuePostFlushCb(() => {
+          instance.hmrUpdated = false
+        })
+      } else if (
         // bail on dynamic slots (v-if, v-for, reference of scope variables)
-        !(vnode.patchFlag & PatchFlags.DYNAMIC_SLOTS) &&
-        // bail on HRM updates
-        !(__DEV__ && instance.parent && instance.parent.renderUpdated)
+        !(vnode.patchFlag & PatchFlags.DYNAMIC_SLOTS)
       ) {
         // compiled AND static.
         // no need to update, and skip stale slots removal.

+ 2 - 2
packages/runtime-core/src/hmr.ts

@@ -70,9 +70,9 @@ function rerender(id: string, newRender?: Function) {
     }
     instance.renderCache = []
     // this flag forces child components with slot content to update
-    instance.renderUpdated = true
+    instance.hmrUpdated = true
     instance.update()
-    instance.renderUpdated = false
+    instance.hmrUpdated = false
   })
 }
 

+ 2 - 2
packages/runtime-core/src/renderer.ts

@@ -779,7 +779,7 @@ function baseCreateRenderer(
       invokeDirectiveHook(n2, n1, parentComponent, 'beforeUpdate')
     }
 
-    if (__DEV__ && parentComponent && parentComponent.renderUpdated) {
+    if (__DEV__ && parentComponent && parentComponent.hmrUpdated) {
       // HMR updated, force full diff
       patchFlag = 0
       optimized = false
@@ -1006,7 +1006,7 @@ function baseCreateRenderer(
       optimized = true
     }
 
-    if (__DEV__ && parentComponent && parentComponent.renderUpdated) {
+    if (__DEV__ && parentComponent && parentComponent.hmrUpdated) {
       // HMR updated, force full diff
       patchFlag = 0
       optimized = false