Skip to content

Commit 9c23ddf

Browse files
committed
fix(hmr): fix updates for imported but not yet rendered components
1 parent 6b424c2 commit 9c23ddf

File tree

2 files changed

+48
-37
lines changed

2 files changed

+48
-37
lines changed

packages/runtime-core/__tests__/hmr.spec.ts

+13-13
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ describe('hot module replacement', () => {
3333
})
3434

3535
test('createRecord', () => {
36-
expect(createRecord('test1')).toBe(true)
36+
expect(createRecord('test1', {})).toBe(true)
3737
// if id has already been created, should return false
38-
expect(createRecord('test1')).toBe(false)
38+
expect(createRecord('test1', {})).toBe(false)
3939
})
4040

4141
test('rerender', async () => {
@@ -47,7 +47,7 @@ describe('hot module replacement', () => {
4747
__hmrId: childId,
4848
render: compileToFunction(`<div><slot/></div>`)
4949
}
50-
createRecord(childId)
50+
createRecord(childId, Child)
5151

5252
const Parent: ComponentOptions = {
5353
__hmrId: parentId,
@@ -59,7 +59,7 @@ describe('hot module replacement', () => {
5959
`<div @click="count++">{{ count }}<Child>{{ count }}</Child></div>`
6060
)
6161
}
62-
createRecord(parentId)
62+
createRecord(parentId, Parent)
6363

6464
render(h(Parent), root)
6565
expect(serializeInner(root)).toBe(`<div>0<div>0</div></div>`)
@@ -125,7 +125,7 @@ describe('hot module replacement', () => {
125125
unmounted: unmountSpy,
126126
render: compileToFunction(`<div @click="count++">{{ count }}</div>`)
127127
}
128-
createRecord(childId)
128+
createRecord(childId, Child)
129129

130130
const Parent: ComponentOptions = {
131131
render: () => h(Child)
@@ -164,7 +164,7 @@ describe('hot module replacement', () => {
164164
render: compileToFunction(`<div @click="count++">{{ count }}</div>`)
165165
}
166166
}
167-
createRecord(childId)
167+
createRecord(childId, Child)
168168

169169
const Parent: ComponentOptions = {
170170
render: () => h(Child)
@@ -209,7 +209,7 @@ describe('hot module replacement', () => {
209209
},
210210
render: compileToFunction(template)
211211
}
212-
createRecord(id)
212+
createRecord(id, Comp)
213213

214214
render(h(Comp), root)
215215
expect(serializeInner(root)).toBe(
@@ -246,14 +246,14 @@ describe('hot module replacement', () => {
246246
},
247247
render: compileToFunction(`<div>{{ msg }}</div>`)
248248
}
249-
createRecord(childId)
249+
createRecord(childId, Child)
250250

251251
const Parent: ComponentOptions = {
252252
__hmrId: parentId,
253253
components: { Child },
254254
render: compileToFunction(`<Child msg="foo" />`)
255255
}
256-
createRecord(parentId)
256+
createRecord(parentId, Parent)
257257

258258
render(h(Parent), root)
259259
expect(serializeInner(root)).toBe(`<div>foo</div>`)
@@ -272,14 +272,14 @@ describe('hot module replacement', () => {
272272
__hmrId: childId,
273273
render: compileToFunction(`<div>child</div>`)
274274
}
275-
createRecord(childId)
275+
createRecord(childId, Child)
276276

277277
const Parent: ComponentOptions = {
278278
__hmrId: parentId,
279279
components: { Child },
280280
render: compileToFunction(`<Child class="test" />`)
281281
}
282-
createRecord(parentId)
282+
createRecord(parentId, Parent)
283283

284284
render(h(Parent), root)
285285
expect(serializeInner(root)).toBe(`<div class="test">child</div>`)
@@ -299,7 +299,7 @@ describe('hot module replacement', () => {
299299
__hmrId: childId,
300300
render: compileToFunction(`<div>child</div>`)
301301
}
302-
createRecord(childId)
302+
createRecord(childId, Child)
303303

304304
const components: ComponentOptions[] = []
305305

@@ -321,7 +321,7 @@ describe('hot module replacement', () => {
321321
}
322322
}
323323

324-
createRecord(parentId)
324+
createRecord(parentId, parentComp)
325325
}
326326

327327
const last = components[components.length - 1]

packages/runtime-core/src/hmr.ts

+35-24
Original file line numberDiff line numberDiff line change
@@ -42,38 +42,48 @@ if (__DEV__ && (__BROWSER__ || __TEST__)) {
4242
} as HMRRuntime
4343
}
4444

45-
type HMRRecord = Set<ComponentInternalInstance>
45+
type HMRRecord = {
46+
component: ComponentOptions
47+
instances: Set<ComponentInternalInstance>
48+
}
4649

4750
const map: Map<string, HMRRecord> = new Map()
4851

4952
export function registerHMR(instance: ComponentInternalInstance) {
5053
const id = instance.type.__hmrId!
5154
let record = map.get(id)
5255
if (!record) {
53-
createRecord(id)
56+
createRecord(id, instance.type as ComponentOptions)
5457
record = map.get(id)!
5558
}
56-
record.add(instance)
59+
record.instances.add(instance)
5760
}
5861

5962
export function unregisterHMR(instance: ComponentInternalInstance) {
60-
map.get(instance.type.__hmrId!)!.delete(instance)
63+
map.get(instance.type.__hmrId!)!.instances.delete(instance)
6164
}
6265

63-
function createRecord(id: string): boolean {
66+
function createRecord(
67+
id: string,
68+
component: ComponentOptions | ClassComponent
69+
): boolean {
6470
if (map.has(id)) {
6571
return false
6672
}
67-
map.set(id, new Set())
73+
map.set(id, {
74+
component: isClassComponent(component) ? component.__vccOpts : component,
75+
instances: new Set()
76+
})
6877
return true
6978
}
7079

7180
function rerender(id: string, newRender?: Function) {
7281
const record = map.get(id)
7382
if (!record) return
83+
if (newRender) record.component.render = newRender
7484
// Array.from creates a snapshot which avoids the set being mutated during
7585
// updates
76-
Array.from(record).forEach(instance => {
86+
Array.from(record.instances).forEach(instance => {
7787
if (newRender) {
7888
instance.render = newRender as InternalRenderFunction
7989
}
@@ -90,26 +100,27 @@ function reload(id: string, newComp: ComponentOptions | ClassComponent) {
90100
if (!record) return
91101
// Array.from creates a snapshot which avoids the set being mutated during
92102
// updates
93-
Array.from(record).forEach(instance => {
94-
const comp = instance.type
95-
if (!hmrDirtyComponents.has(comp)) {
96-
// 1. Update existing comp definition to match new one
97-
newComp = isClassComponent(newComp) ? newComp.__vccOpts : newComp
98-
extend(comp, newComp)
99-
for (const key in comp) {
100-
if (!(key in newComp)) {
101-
delete (comp as any)[key]
102-
}
103+
const { component, instances } = record
104+
105+
if (!hmrDirtyComponents.has(component)) {
106+
// 1. Update existing comp definition to match new one
107+
newComp = isClassComponent(newComp) ? newComp.__vccOpts : newComp
108+
extend(component, newComp)
109+
for (const key in component) {
110+
if (!(key in newComp)) {
111+
delete (component as any)[key]
103112
}
104-
// 2. Mark component dirty. This forces the renderer to replace the component
105-
// on patch.
106-
hmrDirtyComponents.add(comp)
107-
// 3. Make sure to unmark the component after the reload.
108-
queuePostFlushCb(() => {
109-
hmrDirtyComponents.delete(comp)
110-
})
111113
}
114+
// 2. Mark component dirty. This forces the renderer to replace the component
115+
// on patch.
116+
hmrDirtyComponents.add(component)
117+
// 3. Make sure to unmark the component after the reload.
118+
queuePostFlushCb(() => {
119+
hmrDirtyComponents.delete(component)
120+
})
121+
}
112122

123+
Array.from(instances).forEach(instance => {
113124
if (instance.parent) {
114125
// 4. Force the parent instance to re-render. This will cause all updated
115126
// components to be unmounted and re-mounted. Queue the update so that we

0 commit comments

Comments
 (0)