Skip to content

Commit 2408a65

Browse files
committed
fix(hmr): force full update in child component on slot update
1 parent 5ddd9d2 commit 2408a65

File tree

6 files changed

+24
-17
lines changed

6 files changed

+24
-17
lines changed

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

+7-7
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ describe('hot module replacement', () => {
4545

4646
const Child: ComponentOptions = {
4747
__hmrId: childId,
48-
render: compileToFunction(`<slot/>`)
48+
render: compileToFunction(`<div><slot/></div>`)
4949
}
5050
createRecord(childId, Child)
5151

@@ -62,13 +62,13 @@ describe('hot module replacement', () => {
6262
createRecord(parentId, Parent)
6363

6464
render(h(Parent), root)
65-
expect(serializeInner(root)).toBe(`<div>00</div>`)
65+
expect(serializeInner(root)).toBe(`<div>0<div>0</div></div>`)
6666

6767
// Perform some state change. This change should be preserved after the
6868
// re-render!
6969
triggerEvent(root.children[0] as TestElement, 'click')
7070
await nextTick()
71-
expect(serializeInner(root)).toBe(`<div>11</div>`)
71+
expect(serializeInner(root)).toBe(`<div>1<div>1</div></div>`)
7272

7373
// // Update text while preserving state
7474
rerender(
@@ -77,7 +77,7 @@ describe('hot module replacement', () => {
7777
`<div @click="count++">{{ count }}!<Child>{{ count }}</Child></div>`
7878
)
7979
)
80-
expect(serializeInner(root)).toBe(`<div>1!1</div>`)
80+
expect(serializeInner(root)).toBe(`<div>1!<div>1</div></div>`)
8181

8282
// Should force child update on slot content change
8383
rerender(
@@ -86,7 +86,7 @@ describe('hot module replacement', () => {
8686
`<div @click="count++">{{ count }}!<Child>{{ count }}!</Child></div>`
8787
)
8888
)
89-
expect(serializeInner(root)).toBe(`<div>1!1!</div>`)
89+
expect(serializeInner(root)).toBe(`<div>1!<div>1!</div></div>`)
9090

9191
// Should force update element children despite block optimization
9292
rerender(
@@ -97,7 +97,7 @@ describe('hot module replacement', () => {
9797
</div>`
9898
)
9999
)
100-
expect(serializeInner(root)).toBe(`<div>1<span>1</span>1!</div>`)
100+
expect(serializeInner(root)).toBe(`<div>1<span>1</span><div>1!</div></div>`)
101101

102102
// Should force update child slot elements
103103
rerender(
@@ -108,7 +108,7 @@ describe('hot module replacement', () => {
108108
</div>`
109109
)
110110
)
111-
expect(serializeInner(root)).toBe(`<div><span>1</span></div>`)
111+
expect(serializeInner(root)).toBe(`<div><div><span>1</span></div></div>`)
112112
})
113113

114114
test('reload', async () => {

packages/runtime-core/src/component.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ export interface ComponentInternalInstance {
313313
* hmr marker (dev only)
314314
* @internal
315315
*/
316-
renderUpdated?: boolean
316+
hmrUpdated?: boolean
317317
}
318318

319319
const emptyAppContext = createAppContext()

packages/runtime-core/src/componentRenderUtils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ export function shouldUpdateComponent(
251251
__DEV__ &&
252252
(prevChildren || nextChildren) &&
253253
parentComponent &&
254-
parentComponent.renderUpdated
254+
parentComponent.hmrUpdated
255255
) {
256256
return true
257257
}

packages/runtime-core/src/componentSlots.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { warn } from './warning'
1919
import { isKeepAlive } from './components/KeepAlive'
2020
import { withCtx } from './helpers/withRenderContext'
21+
import { queuePostFlushCb } from './scheduler'
2122

2223
export type Slot = (...args: any[]) => VNode[]
2324

@@ -124,11 +125,17 @@ export const updateSlots = (
124125
if (vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
125126
if ((children as RawSlots)._ === 1) {
126127
// compiled slots.
127-
if (
128+
if (__DEV__ && instance.parent && instance.parent.hmrUpdated) {
129+
// Parent was HMR updated so slot content may have changed.
130+
// force update slots and mark instance for hmr as well
131+
extend(slots, children as Slots)
132+
instance.hmrUpdated = true
133+
queuePostFlushCb(() => {
134+
instance.hmrUpdated = false
135+
})
136+
} else if (
128137
// bail on dynamic slots (v-if, v-for, reference of scope variables)
129-
!(vnode.patchFlag & PatchFlags.DYNAMIC_SLOTS) &&
130-
// bail on HRM updates
131-
!(__DEV__ && instance.parent && instance.parent.renderUpdated)
138+
!(vnode.patchFlag & PatchFlags.DYNAMIC_SLOTS)
132139
) {
133140
// compiled AND static.
134141
// no need to update, and skip stale slots removal.

packages/runtime-core/src/hmr.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ function rerender(id: string, newRender?: Function) {
7070
}
7171
instance.renderCache = []
7272
// this flag forces child components with slot content to update
73-
instance.renderUpdated = true
73+
instance.hmrUpdated = true
7474
instance.update()
75-
instance.renderUpdated = false
75+
instance.hmrUpdated = false
7676
})
7777
}
7878

packages/runtime-core/src/renderer.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,7 @@ function baseCreateRenderer(
779779
invokeDirectiveHook(n2, n1, parentComponent, 'beforeUpdate')
780780
}
781781

782-
if (__DEV__ && parentComponent && parentComponent.renderUpdated) {
782+
if (__DEV__ && parentComponent && parentComponent.hmrUpdated) {
783783
// HMR updated, force full diff
784784
patchFlag = 0
785785
optimized = false
@@ -1006,7 +1006,7 @@ function baseCreateRenderer(
10061006
optimized = true
10071007
}
10081008

1009-
if (__DEV__ && parentComponent && parentComponent.renderUpdated) {
1009+
if (__DEV__ && parentComponent && parentComponent.hmrUpdated) {
10101010
// HMR updated, force full diff
10111011
patchFlag = 0
10121012
optimized = false

0 commit comments

Comments
 (0)