Skip to content

Commit b65a6b8

Browse files
authored
fix(runtime-vapor): sync parent component block reference during HMR reload (#13866)
1 parent 3c31b71 commit b65a6b8

File tree

2 files changed

+148
-1
lines changed

2 files changed

+148
-1
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// TODO: port tests from packages/runtime-core/__tests__/hmr.spec.ts
2+
3+
import { type HMRRuntime, ref } from '@vue/runtime-dom'
4+
import { makeRender } from './_utils'
5+
import {
6+
child,
7+
createComponent,
8+
renderEffect,
9+
setText,
10+
template,
11+
} from '@vue/runtime-vapor'
12+
13+
declare var __VUE_HMR_RUNTIME__: HMRRuntime
14+
const { createRecord, reload } = __VUE_HMR_RUNTIME__
15+
16+
const define = makeRender()
17+
18+
describe('hot module replacement', () => {
19+
test('child reload + parent reload', async () => {
20+
const root = document.createElement('div')
21+
const childId = 'test1-child-reload'
22+
const parentId = 'test1-parent-reload'
23+
24+
const { component: Child } = define({
25+
__hmrId: childId,
26+
setup() {
27+
const msg = ref('child')
28+
return { msg }
29+
},
30+
render(ctx) {
31+
const n0 = template(`<div> </div>`)()
32+
const x0 = child(n0 as any)
33+
renderEffect(() => setText(x0 as any, ctx.msg))
34+
return [n0]
35+
},
36+
})
37+
createRecord(childId, Child as any)
38+
39+
const { mount, component: Parent } = define({
40+
__hmrId: parentId,
41+
setup() {
42+
const msg = ref('root')
43+
return { msg }
44+
},
45+
render(ctx) {
46+
const n0 = createComponent(Child)
47+
const n1 = template(`<div> </div>`)()
48+
const x0 = child(n1 as any)
49+
renderEffect(() => setText(x0 as any, ctx.msg))
50+
return [n0, n1]
51+
},
52+
}).create()
53+
createRecord(parentId, Parent as any)
54+
mount(root)
55+
56+
expect(root.innerHTML).toMatchInlineSnapshot(
57+
`"<div>child</div><div>root</div>"`,
58+
)
59+
60+
// reload child
61+
reload(childId, {
62+
__hmrId: childId,
63+
__vapor: true,
64+
setup() {
65+
const msg = ref('child changed')
66+
return { msg }
67+
},
68+
render(ctx: any) {
69+
const n0 = template(`<div> </div>`)()
70+
const x0 = child(n0 as any)
71+
renderEffect(() => setText(x0 as any, ctx.msg))
72+
return [n0]
73+
},
74+
})
75+
expect(root.innerHTML).toMatchInlineSnapshot(
76+
`"<div>child changed</div><div>root</div>"`,
77+
)
78+
79+
// reload child again
80+
reload(childId, {
81+
__hmrId: childId,
82+
__vapor: true,
83+
setup() {
84+
const msg = ref('child changed2')
85+
return { msg }
86+
},
87+
render(ctx: any) {
88+
const n0 = template(`<div> </div>`)()
89+
const x0 = child(n0 as any)
90+
renderEffect(() => setText(x0 as any, ctx.msg))
91+
return [n0]
92+
},
93+
})
94+
expect(root.innerHTML).toMatchInlineSnapshot(
95+
`"<div>child changed2</div><div>root</div>"`,
96+
)
97+
98+
// reload parent
99+
reload(parentId, {
100+
__hmrId: parentId,
101+
__vapor: true,
102+
setup() {
103+
const msg = ref('root changed')
104+
return { msg }
105+
},
106+
render(ctx: any) {
107+
const n0 = createComponent(Child)
108+
const n1 = template(`<div> </div>`)()
109+
const x0 = child(n1 as any)
110+
renderEffect(() => setText(x0 as any, ctx.msg))
111+
return [n0, n1]
112+
},
113+
})
114+
expect(root.innerHTML).toMatchInlineSnapshot(
115+
`"<div>child changed2</div><div>root changed</div>"`,
116+
)
117+
})
118+
})

packages/runtime-vapor/src/hmr.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
mountComponent,
1313
unmountComponent,
1414
} from './component'
15+
import { isArray } from '@vue/shared'
1516

1617
export function hmrRerender(instance: VaporComponentInstance): void {
1718
const normalized = normalizeBlock(instance.block)
@@ -34,7 +35,8 @@ export function hmrReload(
3435
const parent = normalized[0].parentNode!
3536
const anchor = normalized[normalized.length - 1].nextSibling
3637
unmountComponent(instance, parent)
37-
const prev = setCurrentInstance(instance.parent)
38+
const parentInstance = instance.parent as VaporComponentInstance | null
39+
const prev = setCurrentInstance(parentInstance)
3840
const newInstance = createComponent(
3941
newComp,
4042
instance.rawProps,
@@ -43,4 +45,31 @@ export function hmrReload(
4345
)
4446
setCurrentInstance(...prev)
4547
mountComponent(newInstance, parent, anchor)
48+
49+
updateParentBlockOnHmrReload(parentInstance, instance, newInstance)
50+
}
51+
52+
/**
53+
* dev only
54+
* update parentInstance.block to ensure that the correct parent and
55+
* anchor are found during parentInstance HMR rerender/reload, as
56+
* `normalizeBlock` relies on the current instance.block
57+
*/
58+
function updateParentBlockOnHmrReload(
59+
parentInstance: VaporComponentInstance | null,
60+
instance: VaporComponentInstance,
61+
newInstance: VaporComponentInstance,
62+
): void {
63+
if (parentInstance) {
64+
if (parentInstance.block === instance) {
65+
parentInstance.block = newInstance
66+
} else if (isArray(parentInstance.block)) {
67+
for (let i = 0; i < parentInstance.block.length; i++) {
68+
if (parentInstance.block[i] === instance) {
69+
parentInstance.block[i] = newInstance
70+
break
71+
}
72+
}
73+
}
74+
}
4675
}

0 commit comments

Comments
 (0)