fix(hmr): fix updates for imported but not yet rendered components
This commit is contained in:
parent
6b424c258f
commit
9c23ddf9c5
@ -33,9 +33,9 @@ describe('hot module replacement', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('createRecord', () => {
|
test('createRecord', () => {
|
||||||
expect(createRecord('test1')).toBe(true)
|
expect(createRecord('test1', {})).toBe(true)
|
||||||
// if id has already been created, should return false
|
// if id has already been created, should return false
|
||||||
expect(createRecord('test1')).toBe(false)
|
expect(createRecord('test1', {})).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('rerender', async () => {
|
test('rerender', async () => {
|
||||||
@ -47,7 +47,7 @@ describe('hot module replacement', () => {
|
|||||||
__hmrId: childId,
|
__hmrId: childId,
|
||||||
render: compileToFunction(`<div><slot/></div>`)
|
render: compileToFunction(`<div><slot/></div>`)
|
||||||
}
|
}
|
||||||
createRecord(childId)
|
createRecord(childId, Child)
|
||||||
|
|
||||||
const Parent: ComponentOptions = {
|
const Parent: ComponentOptions = {
|
||||||
__hmrId: parentId,
|
__hmrId: parentId,
|
||||||
@ -59,7 +59,7 @@ describe('hot module replacement', () => {
|
|||||||
`<div @click="count++">{{ count }}<Child>{{ count }}</Child></div>`
|
`<div @click="count++">{{ count }}<Child>{{ count }}</Child></div>`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
createRecord(parentId)
|
createRecord(parentId, Parent)
|
||||||
|
|
||||||
render(h(Parent), root)
|
render(h(Parent), root)
|
||||||
expect(serializeInner(root)).toBe(`<div>0<div>0</div></div>`)
|
expect(serializeInner(root)).toBe(`<div>0<div>0</div></div>`)
|
||||||
@ -125,7 +125,7 @@ describe('hot module replacement', () => {
|
|||||||
unmounted: unmountSpy,
|
unmounted: unmountSpy,
|
||||||
render: compileToFunction(`<div @click="count++">{{ count }}</div>`)
|
render: compileToFunction(`<div @click="count++">{{ count }}</div>`)
|
||||||
}
|
}
|
||||||
createRecord(childId)
|
createRecord(childId, Child)
|
||||||
|
|
||||||
const Parent: ComponentOptions = {
|
const Parent: ComponentOptions = {
|
||||||
render: () => h(Child)
|
render: () => h(Child)
|
||||||
@ -164,7 +164,7 @@ describe('hot module replacement', () => {
|
|||||||
render: compileToFunction(`<div @click="count++">{{ count }}</div>`)
|
render: compileToFunction(`<div @click="count++">{{ count }}</div>`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
createRecord(childId)
|
createRecord(childId, Child)
|
||||||
|
|
||||||
const Parent: ComponentOptions = {
|
const Parent: ComponentOptions = {
|
||||||
render: () => h(Child)
|
render: () => h(Child)
|
||||||
@ -209,7 +209,7 @@ describe('hot module replacement', () => {
|
|||||||
},
|
},
|
||||||
render: compileToFunction(template)
|
render: compileToFunction(template)
|
||||||
}
|
}
|
||||||
createRecord(id)
|
createRecord(id, Comp)
|
||||||
|
|
||||||
render(h(Comp), root)
|
render(h(Comp), root)
|
||||||
expect(serializeInner(root)).toBe(
|
expect(serializeInner(root)).toBe(
|
||||||
@ -246,14 +246,14 @@ describe('hot module replacement', () => {
|
|||||||
},
|
},
|
||||||
render: compileToFunction(`<div>{{ msg }}</div>`)
|
render: compileToFunction(`<div>{{ msg }}</div>`)
|
||||||
}
|
}
|
||||||
createRecord(childId)
|
createRecord(childId, Child)
|
||||||
|
|
||||||
const Parent: ComponentOptions = {
|
const Parent: ComponentOptions = {
|
||||||
__hmrId: parentId,
|
__hmrId: parentId,
|
||||||
components: { Child },
|
components: { Child },
|
||||||
render: compileToFunction(`<Child msg="foo" />`)
|
render: compileToFunction(`<Child msg="foo" />`)
|
||||||
}
|
}
|
||||||
createRecord(parentId)
|
createRecord(parentId, Parent)
|
||||||
|
|
||||||
render(h(Parent), root)
|
render(h(Parent), root)
|
||||||
expect(serializeInner(root)).toBe(`<div>foo</div>`)
|
expect(serializeInner(root)).toBe(`<div>foo</div>`)
|
||||||
@ -272,14 +272,14 @@ describe('hot module replacement', () => {
|
|||||||
__hmrId: childId,
|
__hmrId: childId,
|
||||||
render: compileToFunction(`<div>child</div>`)
|
render: compileToFunction(`<div>child</div>`)
|
||||||
}
|
}
|
||||||
createRecord(childId)
|
createRecord(childId, Child)
|
||||||
|
|
||||||
const Parent: ComponentOptions = {
|
const Parent: ComponentOptions = {
|
||||||
__hmrId: parentId,
|
__hmrId: parentId,
|
||||||
components: { Child },
|
components: { Child },
|
||||||
render: compileToFunction(`<Child class="test" />`)
|
render: compileToFunction(`<Child class="test" />`)
|
||||||
}
|
}
|
||||||
createRecord(parentId)
|
createRecord(parentId, Parent)
|
||||||
|
|
||||||
render(h(Parent), root)
|
render(h(Parent), root)
|
||||||
expect(serializeInner(root)).toBe(`<div class="test">child</div>`)
|
expect(serializeInner(root)).toBe(`<div class="test">child</div>`)
|
||||||
@ -299,7 +299,7 @@ describe('hot module replacement', () => {
|
|||||||
__hmrId: childId,
|
__hmrId: childId,
|
||||||
render: compileToFunction(`<div>child</div>`)
|
render: compileToFunction(`<div>child</div>`)
|
||||||
}
|
}
|
||||||
createRecord(childId)
|
createRecord(childId, Child)
|
||||||
|
|
||||||
const components: ComponentOptions[] = []
|
const components: ComponentOptions[] = []
|
||||||
|
|
||||||
@ -321,7 +321,7 @@ describe('hot module replacement', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createRecord(parentId)
|
createRecord(parentId, parentComp)
|
||||||
}
|
}
|
||||||
|
|
||||||
const last = components[components.length - 1]
|
const last = components[components.length - 1]
|
||||||
|
@ -42,7 +42,10 @@ if (__DEV__ && (__BROWSER__ || __TEST__)) {
|
|||||||
} as HMRRuntime
|
} as HMRRuntime
|
||||||
}
|
}
|
||||||
|
|
||||||
type HMRRecord = Set<ComponentInternalInstance>
|
type HMRRecord = {
|
||||||
|
component: ComponentOptions
|
||||||
|
instances: Set<ComponentInternalInstance>
|
||||||
|
}
|
||||||
|
|
||||||
const map: Map<string, HMRRecord> = new Map()
|
const map: Map<string, HMRRecord> = new Map()
|
||||||
|
|
||||||
@ -50,30 +53,37 @@ export function registerHMR(instance: ComponentInternalInstance) {
|
|||||||
const id = instance.type.__hmrId!
|
const id = instance.type.__hmrId!
|
||||||
let record = map.get(id)
|
let record = map.get(id)
|
||||||
if (!record) {
|
if (!record) {
|
||||||
createRecord(id)
|
createRecord(id, instance.type as ComponentOptions)
|
||||||
record = map.get(id)!
|
record = map.get(id)!
|
||||||
}
|
}
|
||||||
record.add(instance)
|
record.instances.add(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unregisterHMR(instance: ComponentInternalInstance) {
|
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)) {
|
if (map.has(id)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
map.set(id, new Set())
|
map.set(id, {
|
||||||
|
component: isClassComponent(component) ? component.__vccOpts : component,
|
||||||
|
instances: new Set()
|
||||||
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
function rerender(id: string, newRender?: Function) {
|
function rerender(id: string, newRender?: Function) {
|
||||||
const record = map.get(id)
|
const record = map.get(id)
|
||||||
if (!record) return
|
if (!record) return
|
||||||
|
if (newRender) record.component.render = newRender
|
||||||
// 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).forEach(instance => {
|
Array.from(record.instances).forEach(instance => {
|
||||||
if (newRender) {
|
if (newRender) {
|
||||||
instance.render = newRender as InternalRenderFunction
|
instance.render = newRender as InternalRenderFunction
|
||||||
}
|
}
|
||||||
@ -90,26 +100,27 @@ function reload(id: string, newComp: ComponentOptions | ClassComponent) {
|
|||||||
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).forEach(instance => {
|
const { component, instances } = record
|
||||||
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]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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(component)
|
||||||
|
// 3. Make sure to unmark the component after the reload.
|
||||||
|
queuePostFlushCb(() => {
|
||||||
|
hmrDirtyComponents.delete(component)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.from(instances).forEach(instance => {
|
||||||
if (instance.parent) {
|
if (instance.parent) {
|
||||||
// 4. 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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user