Skip to content

Commit 7334376

Browse files
committed
fix(hmr): fix hmr error for hoisted children array in v-for
fix #6978 close #7114
1 parent 6021d02 commit 7334376

File tree

6 files changed

+67
-2
lines changed

6 files changed

+67
-2
lines changed

Diff for: packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts

+12
Original file line numberDiff line numberDiff line change
@@ -593,5 +593,17 @@ describe('compiler: hoistStatic transform', () => {
593593
expect(root.hoists.length).toBe(2)
594594
expect(generate(root).code).toMatchSnapshot()
595595
})
596+
597+
test('clone hoisted array children in HMR mode', () => {
598+
const root = transformWithHoist(`<div><span class="hi"></span></div>`, {
599+
hmr: true
600+
})
601+
expect(root.hoists.length).toBe(2)
602+
expect(root.codegenNode).toMatchObject({
603+
children: {
604+
content: '[..._hoisted_2]'
605+
}
606+
})
607+
})
596608
})
597609
})

Diff for: packages/compiler-core/src/options.ts

+7
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,13 @@ export interface TransformOptions
256256
* needed to render inline CSS variables on component root
257257
*/
258258
ssrCssVars?: string
259+
/**
260+
* Whether to compile the template assuming it needs to handle HMR.
261+
* Some edge cases may need to generate different code for HMR to work
262+
* correctly, e.g. #6938, #7138
263+
* @internal
264+
*/
265+
hmr?: boolean
259266
}
260267

261268
export interface CodegenOptions extends SharedTransformCodegenOptions {

Diff for: packages/compiler-core/src/transform.ts

+2
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ export function createTransformContext(
129129
filename = '',
130130
prefixIdentifiers = false,
131131
hoistStatic = false,
132+
hmr = false,
132133
cacheHandlers = false,
133134
nodeTransforms = [],
134135
directiveTransforms = {},
@@ -155,6 +156,7 @@ export function createTransformContext(
155156
selfName: nameMatch && capitalize(camelize(nameMatch[1])),
156157
prefixIdentifiers,
157158
hoistStatic,
159+
hmr,
158160
cacheHandlers,
159161
nodeTransforms,
160162
directiveTransforms,

Diff for: packages/compiler-core/src/transforms/hoistStatic.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,16 @@ function walk(
140140
node.codegenNode.type === NodeTypes.VNODE_CALL &&
141141
isArray(node.codegenNode.children)
142142
) {
143-
node.codegenNode.children = context.hoist(
143+
const hoisted = context.hoist(
144144
createArrayExpression(node.codegenNode.children)
145145
)
146+
// #6978, #7138, #7114
147+
// a hoisted children array inside v-for can caused HMR errors since
148+
// it might be mutated when mounting the v-for list
149+
if (context.hmr) {
150+
hoisted.content = `[...${hoisted.content}]`
151+
}
152+
node.codegenNode.children = hoisted
146153
}
147154
}
148155

Diff for: packages/compiler-sfc/src/compileTemplate.ts

+1
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ function doCompileTemplate({
212212
slotted,
213213
sourceMap: true,
214214
...compilerOptions,
215+
hmr: !isProd,
215216
nodeTransforms: nodeTransforms.concat(compilerOptions.nodeTransforms || []),
216217
filename,
217218
onError: e => errors.push(e),

Diff for: packages/runtime-core/__tests__/hmr.spec.ts

+37-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const { createRecord, rerender, reload } = __VUE_HMR_RUNTIME__
2020
registerRuntimeCompiler(compileToFunction)
2121

2222
function compileToFunction(template: string) {
23-
const { code } = baseCompile(template)
23+
const { code } = baseCompile(template, { hoistStatic: true, hmr: true })
2424
const render = new Function('Vue', code)(
2525
runtimeTest
2626
) as InternalRenderFunction
@@ -567,4 +567,40 @@ describe('hot module replacement', () => {
567567
rerender(parentId, compileToFunction(`<Child>2</Child>`))
568568
expect(serializeInner(root)).toBe(`2`)
569569
})
570+
571+
// #6978, #7138, #7114
572+
test('hoisted children array inside v-for', () => {
573+
const root = nodeOps.createElement('div')
574+
const appId = 'test-app-id'
575+
const App: ComponentOptions = {
576+
__hmrId: appId,
577+
render: compileToFunction(
578+
`<div v-for="item of 2">
579+
<div>1</div>
580+
</div>
581+
<p>2</p>
582+
<p>3</p>`
583+
)
584+
}
585+
createRecord(appId, App)
586+
587+
render(h(App), root)
588+
expect(serializeInner(root)).toBe(
589+
`<div><div>1</div></div><div><div>1</div></div><p>2</p><p>3</p>`
590+
)
591+
592+
// move the <p>3</p> into the <div>1</div>
593+
rerender(
594+
appId,
595+
compileToFunction(
596+
`<div v-for="item of 2">
597+
<div>1<p>3</p></div>
598+
</div>
599+
<p>2</p>`
600+
)
601+
)
602+
expect(serializeInner(root)).toBe(
603+
`<div><div>1<p>3</p></div></div><div><div>1<p>3</p></div></div><p>2</p>`
604+
)
605+
})
570606
})

0 commit comments

Comments
 (0)