diff --git a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
index eec5a76d363..49ad7ad8982 100644
--- a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
@@ -593,5 +593,17 @@ describe('compiler: hoistStatic transform', () => {
expect(root.hoists.length).toBe(2)
expect(generate(root).code).toMatchSnapshot()
})
+
+ test('clone hoisted array children in HMR mode', () => {
+ const root = transformWithHoist(`
`, {
+ hmr: true
+ })
+ expect(root.hoists.length).toBe(2)
+ expect(root.codegenNode).toMatchObject({
+ children: {
+ content: '[..._hoisted_2]'
+ }
+ })
+ })
})
})
diff --git a/packages/compiler-core/src/options.ts b/packages/compiler-core/src/options.ts
index 65bbcb36dd6..6f1a8efcff8 100644
--- a/packages/compiler-core/src/options.ts
+++ b/packages/compiler-core/src/options.ts
@@ -256,6 +256,13 @@ export interface TransformOptions
* needed to render inline CSS variables on component root
*/
ssrCssVars?: string
+ /**
+ * Whether to compile the template assuming it needs to handle HMR.
+ * Some edge cases may need to generate different code for HMR to work
+ * correctly, e.g. #6938, #7138
+ * @internal
+ */
+ hmr?: boolean
}
export interface CodegenOptions extends SharedTransformCodegenOptions {
diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts
index d26c11bba20..04f85679cae 100644
--- a/packages/compiler-core/src/transform.ts
+++ b/packages/compiler-core/src/transform.ts
@@ -129,6 +129,7 @@ export function createTransformContext(
filename = '',
prefixIdentifiers = false,
hoistStatic = false,
+ hmr = false,
cacheHandlers = false,
nodeTransforms = [],
directiveTransforms = {},
@@ -155,6 +156,7 @@ export function createTransformContext(
selfName: nameMatch && capitalize(camelize(nameMatch[1])),
prefixIdentifiers,
hoistStatic,
+ hmr,
cacheHandlers,
nodeTransforms,
directiveTransforms,
diff --git a/packages/compiler-core/src/transforms/hoistStatic.ts b/packages/compiler-core/src/transforms/hoistStatic.ts
index 5526163c6f9..fd443496ca7 100644
--- a/packages/compiler-core/src/transforms/hoistStatic.ts
+++ b/packages/compiler-core/src/transforms/hoistStatic.ts
@@ -140,9 +140,16 @@ function walk(
node.codegenNode.type === NodeTypes.VNODE_CALL &&
isArray(node.codegenNode.children)
) {
- node.codegenNode.children = context.hoist(
+ const hoisted = context.hoist(
createArrayExpression(node.codegenNode.children)
)
+ // #6978, #7138, #7114
+ // a hoisted children array inside v-for can caused HMR errors since
+ // it might be mutated when mounting the v-for list
+ if (context.hmr) {
+ hoisted.content = `[...${hoisted.content}]`
+ }
+ node.codegenNode.children = hoisted
}
}
diff --git a/packages/compiler-sfc/src/compileTemplate.ts b/packages/compiler-sfc/src/compileTemplate.ts
index fbd100c9784..b036619c794 100644
--- a/packages/compiler-sfc/src/compileTemplate.ts
+++ b/packages/compiler-sfc/src/compileTemplate.ts
@@ -212,6 +212,7 @@ function doCompileTemplate({
slotted,
sourceMap: true,
...compilerOptions,
+ hmr: !isProd,
nodeTransforms: nodeTransforms.concat(compilerOptions.nodeTransforms || []),
filename,
onError: e => errors.push(e),
diff --git a/packages/runtime-core/__tests__/hmr.spec.ts b/packages/runtime-core/__tests__/hmr.spec.ts
index db713a3f276..2e989e368a3 100644
--- a/packages/runtime-core/__tests__/hmr.spec.ts
+++ b/packages/runtime-core/__tests__/hmr.spec.ts
@@ -20,7 +20,7 @@ const { createRecord, rerender, reload } = __VUE_HMR_RUNTIME__
registerRuntimeCompiler(compileToFunction)
function compileToFunction(template: string) {
- const { code } = baseCompile(template)
+ const { code } = baseCompile(template, { hoistStatic: true, hmr: true })
const render = new Function('Vue', code)(
runtimeTest
) as InternalRenderFunction
@@ -567,4 +567,40 @@ describe('hot module replacement', () => {
rerender(parentId, compileToFunction(`2`))
expect(serializeInner(root)).toBe(`2`)
})
+
+ // #6978, #7138, #7114
+ test('hoisted children array inside v-for', () => {
+ const root = nodeOps.createElement('div')
+ const appId = 'test-app-id'
+ const App: ComponentOptions = {
+ __hmrId: appId,
+ render: compileToFunction(
+ `
+ 2
+ 3
`
+ )
+ }
+ createRecord(appId, App)
+
+ render(h(App), root)
+ expect(serializeInner(root)).toBe(
+ `2
3
`
+ )
+
+ // move the 3
into the 1
+ rerender(
+ appId,
+ compileToFunction(
+ `
+ 2
`
+ )
+ )
+ expect(serializeInner(root)).toBe(
+ `2
`
+ )
+ })
})