]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(hmr): fix hmr error for hoisted children array in v-for
authorEvan You <yyx990803@gmail.com>
Sat, 21 Oct 2023 13:07:55 +0000 (21:07 +0800)
committerEvan You <yyx990803@gmail.com>
Sat, 21 Oct 2023 13:08:49 +0000 (21:08 +0800)
fix #6978
close #7114

packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
packages/compiler-core/src/options.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/hoistStatic.ts
packages/compiler-sfc/src/compileTemplate.ts
packages/runtime-core/__tests__/hmr.spec.ts

index eec5a76d3637313742bf17dbaa085bfe9418c0f4..49ad7ad8982f5f7f28fe37a91b65f96fd078c842 100644 (file)
@@ -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(`<div><span class="hi"></span></div>`, {
+        hmr: true
+      })
+      expect(root.hoists.length).toBe(2)
+      expect(root.codegenNode).toMatchObject({
+        children: {
+          content: '[..._hoisted_2]'
+        }
+      })
+    })
   })
 })
index 65bbcb36dd600775695bc3e0468577901eceb39a..6f1a8efcff8567c076ac4118b1b1ceee5c631d9d 100644 (file)
@@ -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 {
index d26c11bba2066cb57ad82c32bd94ceca567caa1f..04f85679cae9e01fc588ae639ec15dcad5e28484 100644 (file)
@@ -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,
index 5526163c6f95774f6c0135365218d3cb10a1c0d9..fd443496ca7aeae237e8e67c0a84b9b29386357e 100644 (file)
@@ -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
   }
 }
 
index fbd100c9784371f6d2c80a4f8b1a11d19aaef48d..b036619c7944a3681f395c0273801aa22010ca0d 100644 (file)
@@ -212,6 +212,7 @@ function doCompileTemplate({
     slotted,
     sourceMap: true,
     ...compilerOptions,
+    hmr: !isProd,
     nodeTransforms: nodeTransforms.concat(compilerOptions.nodeTransforms || []),
     filename,
     onError: e => errors.push(e),
index db713a3f27622aac3150734b23f6df50698ef1ad..2e989e368a331383a0fc7d4766d6c7fc2765f47d 100644 (file)
@@ -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(`<Child>2</Child>`))
     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(
+        `<div v-for="item of 2">
+          <div>1</div>
+        </div>
+        <p>2</p>
+        <p>3</p>`
+      )
+    }
+    createRecord(appId, App)
+
+    render(h(App), root)
+    expect(serializeInner(root)).toBe(
+      `<div><div>1</div></div><div><div>1</div></div><p>2</p><p>3</p>`
+    )
+
+    // move the <p>3</p> into the <div>1</div>
+    rerender(
+      appId,
+      compileToFunction(
+        `<div v-for="item of 2">
+          <div>1<p>3</p></div>
+        </div>
+        <p>2</p>`
+      )
+    )
+    expect(serializeInner(root)).toBe(
+      `<div><div>1<p>3</p></div></div><div><div>1<p>3</p></div></div><p>2</p>`
+    )
+  })
 })