Skip to content

Commit 1b946c8

Browse files
committed
fix(hmr): always force full child component props update in HMR mode
1 parent 5b8883a commit 1b946c8

File tree

2 files changed

+81
-7
lines changed

2 files changed

+81
-7
lines changed

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

+78-7
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,13 @@ describe('hot module replacement', () => {
7171
expect(serializeInner(root)).toBe(`<div>11</div>`)
7272

7373
// // Update text while preserving state
74-
// rerender(
75-
// parentId,
76-
// compileToFunction(
77-
// `<div @click="count++">{{ count }}!<Child>{{ count }}</Child></div>`
78-
// )
79-
// )
80-
// expect(serializeInner(root)).toBe(`<div>1!1</div>`)
74+
rerender(
75+
parentId,
76+
compileToFunction(
77+
`<div @click="count++">{{ count }}!<Child>{{ count }}</Child></div>`
78+
)
79+
)
80+
expect(serializeInner(root)).toBe(`<div>1!1</div>`)
8181

8282
// Should force child update on slot content change
8383
rerender(
@@ -147,4 +147,75 @@ describe('hot module replacement', () => {
147147
expect(unmountSpy).toHaveBeenCalledTimes(1)
148148
expect(mountSpy).toHaveBeenCalledTimes(1)
149149
})
150+
151+
// #1156 - static nodes should retain DOM element reference across updates
152+
// when HMR is active
153+
test('static el reference', async () => {
154+
const root = nodeOps.createElement('div')
155+
const id = 'test-static-el'
156+
157+
const template = `<div>
158+
<div>{{ count }}</div>
159+
<button @click="count++">++</button>
160+
</div>`
161+
162+
const Comp: ComponentOptions = {
163+
__hmrId: id,
164+
data() {
165+
return { count: 0 }
166+
},
167+
render: compileToFunction(template)
168+
}
169+
createRecord(id, Comp)
170+
171+
render(h(Comp), root)
172+
expect(serializeInner(root)).toBe(
173+
`<div><div>0</div><button>++</button></div>`
174+
)
175+
176+
// 1. click to trigger update
177+
triggerEvent((root as any).children[0].children[1], 'click')
178+
await nextTick()
179+
expect(serializeInner(root)).toBe(
180+
`<div><div>1</div><button>++</button></div>`
181+
)
182+
183+
// 2. trigger HMR
184+
rerender(
185+
id,
186+
compileToFunction(template.replace(`<button`, `<button class="foo"`))
187+
)
188+
expect(serializeInner(root)).toBe(
189+
`<div><div>1</div><button class="foo">++</button></div>`
190+
)
191+
})
192+
193+
// #1157 - component should force full props update when HMR is active
194+
test('force update child component w/ static props', () => {
195+
const root = nodeOps.createElement('div')
196+
const parentId = 'test2-parent'
197+
const childId = 'test2-child'
198+
199+
const Child: ComponentOptions = {
200+
__hmrId: childId,
201+
props: {
202+
msg: String
203+
},
204+
render: compileToFunction(`<div>{{ msg }}</div>`)
205+
}
206+
createRecord(childId, Child)
207+
208+
const Parent: ComponentOptions = {
209+
__hmrId: parentId,
210+
components: { Child },
211+
render: compileToFunction(`<Child msg="foo" />`)
212+
}
213+
createRecord(parentId, Parent)
214+
215+
render(h(Parent), root)
216+
expect(serializeInner(root)).toBe(`<div>foo</div>`)
217+
218+
rerender(parentId, compileToFunction(`<Child msg="bar" />`))
219+
expect(serializeInner(root)).toBe(`<div>bar</div>`)
220+
})
150221
})

packages/runtime-core/src/renderer.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1270,6 +1270,9 @@ function baseCreateRenderer(
12701270
nextVNode: VNode,
12711271
optimized: boolean
12721272
) => {
1273+
if (__DEV__ && instance.type.__hmrId) {
1274+
optimized = false
1275+
}
12731276
nextVNode.component = instance
12741277
const prevProps = instance.vnode.props
12751278
instance.vnode = nextVNode

0 commit comments

Comments
 (0)