fix(hmr): handle cases where instances with same id having different definitions

This commit is contained in:
Evan You 2020-05-03 13:52:09 -04:00
parent 0c48558f4c
commit 01b7e90eac

View File

@ -33,10 +33,7 @@ if (__DEV__) {
} as HMRRuntime } as HMRRuntime
} }
interface HMRRecord { type HMRRecord = Set<ComponentInternalInstance>
comp: ComponentOptions
instances: Set<ComponentInternalInstance>
}
const map: Map<string, HMRRecord> = new Map() const map: Map<string, HMRRecord> = new Map()
@ -47,21 +44,18 @@ export function registerHMR(instance: ComponentInternalInstance) {
createRecord(id, instance.type as ComponentOptions) createRecord(id, instance.type as ComponentOptions)
record = map.get(id)! record = map.get(id)!
} }
record.instances.add(instance) record.add(instance)
} }
export function unregisterHMR(instance: ComponentInternalInstance) { export function unregisterHMR(instance: ComponentInternalInstance) {
map.get(instance.type.__hmrId!)!.instances.delete(instance) map.get(instance.type.__hmrId!)!.delete(instance)
} }
function createRecord(id: string, comp: ComponentOptions): boolean { function createRecord(id: string, comp: ComponentOptions): boolean {
if (map.has(id)) { if (map.has(id)) {
return false return false
} }
map.set(id, { map.set(id, new Set())
comp,
instances: new Set()
})
return true return true
} }
@ -70,7 +64,7 @@ function rerender(id: string, newRender?: Function) {
if (!record) return if (!record) return
// Array.from creates a snapshot which avoids the set being mutated during // Array.from creates a snapshot which avoids the set being mutated during
// updates // updates
Array.from(record.instances).forEach(instance => { Array.from(record).forEach(instance => {
if (newRender) { if (newRender) {
instance.render = newRender as InternalRenderFunction instance.render = newRender as InternalRenderFunction
} }
@ -85,22 +79,29 @@ function rerender(id: string, newRender?: Function) {
function reload(id: string, newComp: ComponentOptions) { function reload(id: string, newComp: ComponentOptions) {
const record = map.get(id) const record = map.get(id)
if (!record) return if (!record) return
// 1. Update existing comp definition to match new one
const comp = record.comp
Object.assign(comp, newComp)
for (const key in comp) {
if (!(key in newComp)) {
delete (comp as any)[key]
}
}
// 2. Mark component dirty. This forces the renderer to replace the component
// on patch.
comp.__hmrUpdated = true
// Array.from creates a snapshot which avoids the set being mutated during // Array.from creates a snapshot which avoids the set being mutated during
// updates // updates
Array.from(record.instances).forEach(instance => { Array.from(record).forEach(instance => {
const comp = instance.type
if (!comp.__hmrUpdated) {
// 1. Update existing comp definition to match new one
Object.assign(comp, newComp)
for (const key in comp) {
if (!(key in newComp)) {
delete (comp as any)[key]
}
}
// 2. Mark component dirty. This forces the renderer to replace the component
// on patch.
comp.__hmrUpdated = true
// 3. Make sure to unmark the component after the reload.
queuePostFlushCb(() => {
comp.__hmrUpdated = false
})
}
if (instance.parent) { if (instance.parent) {
// 3. Force the parent instance to re-render. This will cause all updated // 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 // 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. // don't end up forcing the same parent to re-render multiple times.
queueJob(instance.parent.update) queueJob(instance.parent.update)
@ -116,10 +117,6 @@ function reload(id: string, newComp: ComponentOptions) {
) )
} }
}) })
// 4. Make sure to unmark the component after the reload.
queuePostFlushCb(() => {
comp.__hmrUpdated = false
})
} }
function tryWrap(fn: (id: string, arg: any) => any): Function { function tryWrap(fn: (id: string, arg: any) => any): Function {