diff --git a/packages/runtime-core/__tests__/hmr.spec.ts b/packages/runtime-core/__tests__/hmr.spec.ts
index a5d0d67d..0a5821c8 100644
--- a/packages/runtime-core/__tests__/hmr.spec.ts
+++ b/packages/runtime-core/__tests__/hmr.spec.ts
@@ -36,9 +36,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 () => {
@@ -50,7 +50,7 @@ describe('hot module replacement', () => {
__hmrId: childId,
render: compileToFunction(`
`)
}
- createRecord(childId, Child)
+ createRecord(childId)
const Parent: ComponentOptions = {
__hmrId: parentId,
@@ -62,7 +62,7 @@ describe('hot module replacement', () => {
`{{ count }}{{ count }}
`
)
}
- createRecord(parentId, Parent)
+ createRecord(parentId)
render(h(Parent), root)
expect(serializeInner(root)).toBe(``)
@@ -128,7 +128,7 @@ describe('hot module replacement', () => {
unmounted: unmountSpy,
render: compileToFunction(`{{ count }}
`)
}
- createRecord(childId, Child)
+ createRecord(childId)
const Parent: ComponentOptions = {
render: () => h(Child)
@@ -167,7 +167,7 @@ describe('hot module replacement', () => {
render: compileToFunction(`{{ count }}
`)
}
}
- createRecord(childId, Child)
+ createRecord(childId)
const Parent: ComponentOptions = {
render: () => h(Child)
@@ -212,7 +212,7 @@ describe('hot module replacement', () => {
},
render: compileToFunction(template)
}
- createRecord(id, Comp)
+ createRecord(id)
render(h(Comp), root)
expect(serializeInner(root)).toBe(
@@ -249,14 +249,14 @@ describe('hot module replacement', () => {
},
render: compileToFunction(`{{ msg }}
`)
}
- createRecord(childId, Child)
+ createRecord(childId)
const Parent: ComponentOptions = {
__hmrId: parentId,
components: { Child },
render: compileToFunction(``)
}
- createRecord(parentId, Parent)
+ createRecord(parentId)
render(h(Parent), root)
expect(serializeInner(root)).toBe(`foo
`)
@@ -275,14 +275,14 @@ describe('hot module replacement', () => {
__hmrId: childId,
render: compileToFunction(`child
`)
}
- createRecord(childId, Child)
+ createRecord(childId)
const Parent: ComponentOptions = {
__hmrId: parentId,
components: { Child },
render: compileToFunction(``)
}
- createRecord(parentId, Parent)
+ createRecord(parentId)
render(h(Parent), root)
expect(serializeInner(root)).toBe(`child
`)
@@ -302,7 +302,7 @@ describe('hot module replacement', () => {
__hmrId: childId,
render: compileToFunction(`child
`)
}
- createRecord(childId, Child)
+ createRecord(childId)
const components: ComponentOptions[] = []
@@ -324,7 +324,7 @@ describe('hot module replacement', () => {
}
}
- createRecord(parentId, parentComp)
+ createRecord(parentId)
}
const last = components[components.length - 1]
@@ -370,7 +370,7 @@ describe('hot module replacement', () => {
`)
}
- createRecord(parentId, Parent)
+ createRecord(parentId)
render(h(Parent), root)
expect(serializeInner(root)).toBe(
@@ -410,7 +410,7 @@ describe('hot module replacement', () => {
return h('div')
}
}
- createRecord(childId, Child)
+ createRecord(childId)
const Parent: ComponentOptions = {
render: () => h(Child)
diff --git a/packages/runtime-core/src/hmr.ts b/packages/runtime-core/src/hmr.ts
index c2fbee65..358fa843 100644
--- a/packages/runtime-core/src/hmr.ts
+++ b/packages/runtime-core/src/hmr.ts
@@ -9,7 +9,6 @@ import {
} from './component'
import { queueJob, queuePostFlushCb } from './scheduler'
import { extend } from '@vue/shared'
-import { warn } from './warning'
export let isHmrUpdating = false
@@ -43,58 +42,46 @@ if (__DEV__) {
} as HMRRuntime
}
-type HMRRecord = {
- component: ComponentOptions
- instances: Set
-}
-
-const map: Map = new Map()
+const map: Map> = new Map()
export function registerHMR(instance: ComponentInternalInstance) {
const id = instance.type.__hmrId!
let record = map.get(id)
if (!record) {
- createRecord(id, instance.type as ComponentOptions)
+ createRecord(id)
record = map.get(id)!
}
- record.instances.add(instance)
+ record.add(instance)
}
export function unregisterHMR(instance: ComponentInternalInstance) {
- map.get(instance.type.__hmrId!)!.instances.delete(instance)
+ map.get(instance.type.__hmrId!)!.delete(instance)
}
-function createRecord(
- id: string,
- component: ComponentOptions | ClassComponent
-): boolean {
- if (!component) {
- warn(
- `HMR API usage is out of date.\n` +
- `Please upgrade vue-loader/vite/rollup-plugin-vue or other relevant ` +
- `dependency that handles Vue SFC compilation.`
- )
- component = {}
- }
+function createRecord(id: string): boolean {
if (map.has(id)) {
return false
}
- map.set(id, {
- component: isClassComponent(component) ? component.__vccOpts : component,
- instances: new Set()
- })
+ map.set(id, new Set())
return true
}
+type HMRComponent = ComponentOptions | ClassComponent
+
+function normalizeClassComponent(component: HMRComponent): ComponentOptions {
+ return isClassComponent(component) ? component.__vccOpts : component
+}
+
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.instances).forEach(instance => {
+ if (!record) {
+ return
+ }
+ // Create a snapshot which avoids the set being mutated during updates
+ ;[...record].forEach(instance => {
if (newRender) {
instance.render = newRender as InternalRenderFunction
+ normalizeClassComponent(instance.type as HMRComponent).render = newRender
}
instance.renderCache = []
// this flag forces child components with slot content to update
@@ -104,40 +91,40 @@ function rerender(id: string, newRender?: Function) {
})
}
-function reload(id: string, newComp: ComponentOptions | ClassComponent) {
+function reload(id: string, newComp: HMRComponent) {
const record = map.get(id)
if (!record) return
- // Array.from creates a snapshot which avoids the set being mutated during
- // updates
- 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 !== '__file' && !(key in newComp)) {
- delete (component as any)[key]
+ newComp = normalizeClassComponent(newComp)
+
+ // create a snapshot which avoids the set being mutated during updates
+ const instances = [...record]
+
+ for (const instance of instances) {
+ const oldComp = normalizeClassComponent(instance.type as HMRComponent)
+
+ if (!hmrDirtyComponents.has(oldComp)) {
+ // 1. Update existing comp definition to match new one
+ extend(oldComp, newComp)
+ for (const key in oldComp) {
+ if (key !== '__file' && !(key in newComp)) {
+ delete (oldComp as any)[key]
+ }
}
+ // 2. mark definition dirty. This forces the renderer to replace the
+ // component on patch.
+ hmrDirtyComponents.add(oldComp)
}
- // 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 => {
- // invalidate options resolution cache
+ // 3. invalidate options resolution cache
instance.appContext.optionsCache.delete(instance.type as any)
+ // 4. actually update
if (instance.ceReload) {
// custom element
- hmrDirtyComponents.add(component)
+ hmrDirtyComponents.add(oldComp)
instance.ceReload((newComp as any).styles)
- hmrDirtyComponents.delete(component)
+ hmrDirtyComponents.delete(oldComp)
} else 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
@@ -162,6 +149,15 @@ function reload(id: string, newComp: ComponentOptions | ClassComponent) {
'[HMR] Root or manually mounted instance modified. Full reload required.'
)
}
+ }
+
+ // 5. make sure to cleanup dirty hmr components after update
+ queuePostFlushCb(() => {
+ for (const instance of instances) {
+ hmrDirtyComponents.delete(
+ normalizeClassComponent(instance.type as HMRComponent)
+ )
+ }
})
}