]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(hmr): fix updates for imported but not yet rendered components
authorEvan You <yyx990803@gmail.com>
Mon, 26 Oct 2020 21:52:16 +0000 (17:52 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 26 Oct 2020 21:52:27 +0000 (17:52 -0400)
packages/runtime-core/__tests__/hmr.spec.ts
packages/runtime-core/src/hmr.ts

index 31c910d17d46e5029913abf4dbe6c81c095660c6..ab2de223cdc80d3979cd86c404b7f807d44953eb 100644 (file)
@@ -33,9 +33,9 @@ describe('hot module replacement', () => {
   })
 
   test('createRecord', () => {
-    expect(createRecord('test1')).toBe(true)
+    expect(createRecord('test1', {})).toBe(true)
     // if id has already been created, should return false
-    expect(createRecord('test1')).toBe(false)
+    expect(createRecord('test1', {})).toBe(false)
   })
 
   test('rerender', async () => {
@@ -47,7 +47,7 @@ describe('hot module replacement', () => {
       __hmrId: childId,
       render: compileToFunction(`<div><slot/></div>`)
     }
-    createRecord(childId)
+    createRecord(childId, Child)
 
     const Parent: ComponentOptions = {
       __hmrId: parentId,
@@ -59,7 +59,7 @@ describe('hot module replacement', () => {
         `<div @click="count++">{{ count }}<Child>{{ count }}</Child></div>`
       )
     }
-    createRecord(parentId)
+    createRecord(parentId, Parent)
 
     render(h(Parent), root)
     expect(serializeInner(root)).toBe(`<div>0<div>0</div></div>`)
@@ -125,7 +125,7 @@ describe('hot module replacement', () => {
       unmounted: unmountSpy,
       render: compileToFunction(`<div @click="count++">{{ count }}</div>`)
     }
-    createRecord(childId)
+    createRecord(childId, Child)
 
     const Parent: ComponentOptions = {
       render: () => h(Child)
@@ -164,7 +164,7 @@ describe('hot module replacement', () => {
         render: compileToFunction(`<div @click="count++">{{ count }}</div>`)
       }
     }
-    createRecord(childId)
+    createRecord(childId, Child)
 
     const Parent: ComponentOptions = {
       render: () => h(Child)
@@ -209,7 +209,7 @@ describe('hot module replacement', () => {
       },
       render: compileToFunction(template)
     }
-    createRecord(id)
+    createRecord(id, Comp)
 
     render(h(Comp), root)
     expect(serializeInner(root)).toBe(
@@ -246,14 +246,14 @@ describe('hot module replacement', () => {
       },
       render: compileToFunction(`<div>{{ msg }}</div>`)
     }
-    createRecord(childId)
+    createRecord(childId, Child)
 
     const Parent: ComponentOptions = {
       __hmrId: parentId,
       components: { Child },
       render: compileToFunction(`<Child msg="foo" />`)
     }
-    createRecord(parentId)
+    createRecord(parentId, Parent)
 
     render(h(Parent), root)
     expect(serializeInner(root)).toBe(`<div>foo</div>`)
@@ -272,14 +272,14 @@ describe('hot module replacement', () => {
       __hmrId: childId,
       render: compileToFunction(`<div>child</div>`)
     }
-    createRecord(childId)
+    createRecord(childId, Child)
 
     const Parent: ComponentOptions = {
       __hmrId: parentId,
       components: { Child },
       render: compileToFunction(`<Child class="test" />`)
     }
-    createRecord(parentId)
+    createRecord(parentId, Parent)
 
     render(h(Parent), root)
     expect(serializeInner(root)).toBe(`<div class="test">child</div>`)
@@ -299,7 +299,7 @@ describe('hot module replacement', () => {
       __hmrId: childId,
       render: compileToFunction(`<div>child</div>`)
     }
-    createRecord(childId)
+    createRecord(childId, Child)
 
     const components: ComponentOptions[] = []
 
@@ -321,7 +321,7 @@ describe('hot module replacement', () => {
         }
       }
 
-      createRecord(parentId)
+      createRecord(parentId, parentComp)
     }
 
     const last = components[components.length - 1]
index faf4cb24e72f74b955b1a6f4cd6a2dd388a79861..9a2d20def5173ce438456c28df4f17f324a769b8 100644 (file)
@@ -42,7 +42,10 @@ if (__DEV__ && (__BROWSER__ || __TEST__)) {
   } as HMRRuntime
 }
 
-type HMRRecord = Set<ComponentInternalInstance>
+type HMRRecord = {
+  component: ComponentOptions
+  instances: Set<ComponentInternalInstance>
+}
 
 const map: Map<string, HMRRecord> = new Map()
 
@@ -50,30 +53,37 @@ export function registerHMR(instance: ComponentInternalInstance) {
   const id = instance.type.__hmrId!
   let record = map.get(id)
   if (!record) {
-    createRecord(id)
+    createRecord(id, instance.type as ComponentOptions)
     record = map.get(id)!
   }
-  record.add(instance)
+  record.instances.add(instance)
 }
 
 export function unregisterHMR(instance: ComponentInternalInstance) {
-  map.get(instance.type.__hmrId!)!.delete(instance)
+  map.get(instance.type.__hmrId!)!.instances.delete(instance)
 }
 
-function createRecord(id: string): boolean {
+function createRecord(
+  id: string,
+  component: ComponentOptions | ClassComponent
+): boolean {
   if (map.has(id)) {
     return false
   }
-  map.set(id, new Set())
+  map.set(id, {
+    component: isClassComponent(component) ? component.__vccOpts : component,
+    instances: new Set()
+  })
   return true
 }
 
 function rerender(id: string, newRender?: Function) {
   const record = map.get(id)
   if (!record) return
+  if (newRender) record.component.render = newRender
   // Array.from creates a snapshot which avoids the set being mutated during
   // updates
-  Array.from(record).forEach(instance => {
+  Array.from(record.instances).forEach(instance => {
     if (newRender) {
       instance.render = newRender as InternalRenderFunction
     }
@@ -90,26 +100,27 @@ function reload(id: string, newComp: ComponentOptions | ClassComponent) {
   if (!record) return
   // Array.from creates a snapshot which avoids the set being mutated during
   // updates
-  Array.from(record).forEach(instance => {
-    const comp = instance.type
-    if (!hmrDirtyComponents.has(comp)) {
-      // 1. Update existing comp definition to match new one
-      newComp = isClassComponent(newComp) ? newComp.__vccOpts : newComp
-      extend(comp, newComp)
-      for (const key in comp) {
-        if (!(key in newComp)) {
-          delete (comp as any)[key]
-        }
+  const { component, instances } = record
+
+  if (!hmrDirtyComponents.has(component)) {
+    // 1. Update existing comp definition to match new one
+    newComp = isClassComponent(newComp) ? newComp.__vccOpts : newComp
+    extend(component, newComp)
+    for (const key in component) {
+      if (!(key in newComp)) {
+        delete (component as any)[key]
       }
-      // 2. Mark component dirty. This forces the renderer to replace the component
-      // on patch.
-      hmrDirtyComponents.add(comp)
-      // 3. Make sure to unmark the component after the reload.
-      queuePostFlushCb(() => {
-        hmrDirtyComponents.delete(comp)
-      })
     }
+    // 2. Mark component dirty. This forces the renderer to replace the component
+    // on patch.
+    hmrDirtyComponents.add(component)
+    // 3. Make sure to unmark the component after the reload.
+    queuePostFlushCb(() => {
+      hmrDirtyComponents.delete(component)
+    })
+  }
 
+  Array.from(instances).forEach(instance => {
     if (instance.parent) {
       // 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