parent
6bcb7a5ea3
commit
9e3d7731c7
@ -36,9 +36,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 () => {
|
||||||
@ -50,7 +50,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,
|
||||||
@ -62,7 +62,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>`)
|
||||||
@ -128,7 +128,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)
|
||||||
@ -167,7 +167,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)
|
||||||
@ -212,7 +212,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(
|
||||||
@ -249,14 +249,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>`)
|
||||||
@ -275,14 +275,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>`)
|
||||||
@ -302,7 +302,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[] = []
|
||||||
|
|
||||||
@ -324,7 +324,7 @@ describe('hot module replacement', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createRecord(parentId)
|
createRecord(parentId, parentComp)
|
||||||
}
|
}
|
||||||
|
|
||||||
const last = components[components.length - 1]
|
const last = components[components.length - 1]
|
||||||
@ -370,7 +370,7 @@ describe('hot module replacement', () => {
|
|||||||
</Child>
|
</Child>
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
createRecord(parentId)
|
createRecord(parentId, Parent)
|
||||||
|
|
||||||
render(h(Parent), root)
|
render(h(Parent), root)
|
||||||
expect(serializeInner(root)).toBe(
|
expect(serializeInner(root)).toBe(
|
||||||
@ -410,7 +410,7 @@ describe('hot module replacement', () => {
|
|||||||
return h('div')
|
return h('div')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
createRecord(childId)
|
createRecord(childId, Child)
|
||||||
|
|
||||||
const Parent: ComponentOptions = {
|
const Parent: ComponentOptions = {
|
||||||
render: () => h(Child)
|
render: () => h(Child)
|
||||||
@ -435,4 +435,38 @@ describe('hot module replacement', () => {
|
|||||||
expect(createSpy1).toHaveBeenCalledTimes(1)
|
expect(createSpy1).toHaveBeenCalledTimes(1)
|
||||||
expect(createSpy2).toHaveBeenCalledTimes(1)
|
expect(createSpy2).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #4757
|
||||||
|
test('rerender for component that has no active instance yet', () => {
|
||||||
|
const id = 'no-active-instance-rerender'
|
||||||
|
const Foo: ComponentOptions = {
|
||||||
|
__hmrId: id,
|
||||||
|
render: () => 'foo'
|
||||||
|
}
|
||||||
|
|
||||||
|
createRecord(id, Foo)
|
||||||
|
rerender(id, () => 'bar')
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Foo), root)
|
||||||
|
expect(serializeInner(root)).toBe('bar')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('reload for component that has no active instance yet', () => {
|
||||||
|
const id = 'no-active-instance-reload'
|
||||||
|
const Foo: ComponentOptions = {
|
||||||
|
__hmrId: id,
|
||||||
|
render: () => 'foo'
|
||||||
|
}
|
||||||
|
|
||||||
|
createRecord(id, Foo)
|
||||||
|
reload(id, {
|
||||||
|
__hmrId: id,
|
||||||
|
render: () => 'bar'
|
||||||
|
})
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Foo), root)
|
||||||
|
expect(serializeInner(root)).toBe('bar')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -10,6 +10,8 @@ import {
|
|||||||
import { queueJob, queuePostFlushCb } from './scheduler'
|
import { queueJob, queuePostFlushCb } from './scheduler'
|
||||||
import { extend, getGlobalThis } from '@vue/shared'
|
import { extend, getGlobalThis } from '@vue/shared'
|
||||||
|
|
||||||
|
type HMRComponent = ComponentOptions | ClassComponent
|
||||||
|
|
||||||
export let isHmrUpdating = false
|
export let isHmrUpdating = false
|
||||||
|
|
||||||
export const hmrDirtyComponents = new Set<ConcreteComponent>()
|
export const hmrDirtyComponents = new Set<ConcreteComponent>()
|
||||||
@ -33,32 +35,42 @@ if (__DEV__) {
|
|||||||
} as HMRRuntime
|
} as HMRRuntime
|
||||||
}
|
}
|
||||||
|
|
||||||
const map: Map<string, Set<ComponentInternalInstance>> = new Map()
|
const map: Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
// the initial component definition is recorded on import - this allows us
|
||||||
|
// to apply hot updates to the component even when there are no actively
|
||||||
|
// rendered instance.
|
||||||
|
initialDef: ComponentOptions
|
||||||
|
instances: Set<ComponentInternalInstance>
|
||||||
|
}
|
||||||
|
> = new Map()
|
||||||
|
|
||||||
export function registerHMR(instance: ComponentInternalInstance) {
|
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 HMRComponent)
|
||||||
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, initialDef: HMRComponent): boolean {
|
||||||
if (map.has(id)) {
|
if (map.has(id)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
map.set(id, new Set())
|
map.set(id, {
|
||||||
|
initialDef: normalizeClassComponent(initialDef),
|
||||||
|
instances: new Set()
|
||||||
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
type HMRComponent = ComponentOptions | ClassComponent
|
|
||||||
|
|
||||||
function normalizeClassComponent(component: HMRComponent): ComponentOptions {
|
function normalizeClassComponent(component: HMRComponent): ComponentOptions {
|
||||||
return isClassComponent(component) ? component.__vccOpts : component
|
return isClassComponent(component) ? component.__vccOpts : component
|
||||||
}
|
}
|
||||||
@ -68,8 +80,12 @@ function rerender(id: string, newRender?: Function) {
|
|||||||
if (!record) {
|
if (!record) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update initial record (for not-yet-rendered component)
|
||||||
|
record.initialDef.render = newRender
|
||||||
|
|
||||||
// Create a snapshot which avoids the set being mutated during updates
|
// Create a snapshot which avoids the set being mutated during updates
|
||||||
;[...record].forEach(instance => {
|
;[...record.instances].forEach(instance => {
|
||||||
if (newRender) {
|
if (newRender) {
|
||||||
instance.render = newRender as InternalRenderFunction
|
instance.render = newRender as InternalRenderFunction
|
||||||
normalizeClassComponent(instance.type as HMRComponent).render = newRender
|
normalizeClassComponent(instance.type as HMRComponent).render = newRender
|
||||||
@ -87,20 +103,19 @@ function reload(id: string, newComp: HMRComponent) {
|
|||||||
if (!record) return
|
if (!record) return
|
||||||
|
|
||||||
newComp = normalizeClassComponent(newComp)
|
newComp = normalizeClassComponent(newComp)
|
||||||
|
// update initial def (for not-yet-rendered components)
|
||||||
|
updateComponentDef(record.initialDef, newComp)
|
||||||
|
|
||||||
// create a snapshot which avoids the set being mutated during updates
|
// create a snapshot which avoids the set being mutated during updates
|
||||||
const instances = [...record]
|
const instances = [...record.instances]
|
||||||
|
|
||||||
for (const instance of instances) {
|
for (const instance of instances) {
|
||||||
const oldComp = normalizeClassComponent(instance.type as HMRComponent)
|
const oldComp = normalizeClassComponent(instance.type as HMRComponent)
|
||||||
|
|
||||||
if (!hmrDirtyComponents.has(oldComp)) {
|
if (!hmrDirtyComponents.has(oldComp)) {
|
||||||
// 1. Update existing comp definition to match new one
|
// 1. Update existing comp definition to match new one
|
||||||
extend(oldComp, newComp)
|
if (oldComp !== record.initialDef) {
|
||||||
for (const key in oldComp) {
|
updateComponentDef(oldComp, newComp)
|
||||||
if (key !== '__file' && !(key in newComp)) {
|
|
||||||
delete (oldComp as any)[key]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 2. mark definition dirty. This forces the renderer to replace the
|
// 2. mark definition dirty. This forces the renderer to replace the
|
||||||
// component on patch.
|
// component on patch.
|
||||||
@ -152,6 +167,18 @@ function reload(id: string, newComp: HMRComponent) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateComponentDef(
|
||||||
|
oldComp: ComponentOptions,
|
||||||
|
newComp: ComponentOptions
|
||||||
|
) {
|
||||||
|
extend(oldComp, newComp)
|
||||||
|
for (const key in oldComp) {
|
||||||
|
if (key !== '__file' && !(key in newComp)) {
|
||||||
|
delete (oldComp as any)[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function tryWrap(fn: (id: string, arg: any) => any): Function {
|
function tryWrap(fn: (id: string, arg: any) => any): Function {
|
||||||
return (id: string, arg: any) => {
|
return (id: string, arg: any) => {
|
||||||
try {
|
try {
|
||||||
|
Loading…
Reference in New Issue
Block a user