]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-core): fix keep-alive when used in templates
authorEvan You <yyx990803@gmail.com>
Mon, 10 Feb 2020 21:49:37 +0000 (16:49 -0500)
committerEvan You <yyx990803@gmail.com>
Mon, 10 Feb 2020 21:49:37 +0000 (16:49 -0500)
fix #715

packages/compiler-core/__tests__/transforms/transformElement.spec.ts
packages/compiler-core/src/transforms/transformElement.ts

index dc581f0f09de1d1b7d78a72488ef4415a0ee26f0..048a5693e60cbcdf076edb7da1c591f4609fbeea 100644 (file)
@@ -346,18 +346,26 @@ describe('compiler: element transform', () => {
 
   test('should handle <KeepAlive>', () => {
     function assert(tag: string) {
-      const { root, node } = parseWithElementTransform(
-        `<${tag}><span /></${tag}>`
-      )
+      const root = parse(`<div><${tag}><span /></${tag}></div>`)
+      transform(root, {
+        nodeTransforms: [transformElement, transformText]
+      })
       expect(root.components.length).toBe(0)
       expect(root.helpers).toContain(KEEP_ALIVE)
-      expect(node.callee).toBe(CREATE_VNODE)
-      expect(node.arguments).toMatchObject([
-        KEEP_ALIVE,
-        `null`,
-        // keep-alive should not compile content to slots
-        [{ type: NodeTypes.ELEMENT, tag: 'span' }]
-      ])
+      const node = (root.children[0] as any).children[0].codegenNode
+      expect(node.type).toBe(NodeTypes.JS_SEQUENCE_EXPRESSION)
+      expect(node.expressions[1]).toMatchObject({
+        type: NodeTypes.JS_CALL_EXPRESSION,
+        callee: CREATE_BLOCK, // should be forced into a block
+        arguments: [
+          KEEP_ALIVE,
+          `null`,
+          // keep-alive should not compile content to slots
+          [{ type: NodeTypes.ELEMENT, tag: 'span' }],
+          // should get a dynamic slots flag to force updates
+          genFlagText(PatchFlags.DYNAMIC_SLOTS)
+        ]
+      })
     }
 
     assert(`keep-alive`)
index 1d685dce59b5ad648e235e93378f2f1859e650d2..f12f220493898b9d1d630a4b1712d966c5e6b3eb 100644 (file)
@@ -67,7 +67,7 @@ export const transformElement: NodeTransform = (node, context) => {
     // updates inside get proper isSVG flag at runtime. (#639, #643)
     // This is technically web-specific, but splitting the logic out of core
     // leads to too much unnecessary complexity.
-    const shouldUseBlock =
+    let shouldUseBlock =
       !isComponent && (tag === 'svg' || tag === 'foreignObject')
 
     const nodeType = isComponent
@@ -101,21 +101,35 @@ export const transformElement: NodeTransform = (node, context) => {
         args.push(`null`)
       }
 
-      if (__DEV__ && nodeType === KEEP_ALIVE && node.children.length > 1) {
-        context.onError(
-          createCompilerError(ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN, {
-            start: node.children[0].loc.start,
-            end: node.children[node.children.length - 1].loc.end,
-            source: ''
-          })
-        )
+      if (nodeType === KEEP_ALIVE) {
+        // Although a built-in component, we compile KeepAlive with raw children
+        // instead of slot functions so that it can be used inside Transition
+        // or other Transition-wrapping HOCs.
+        // To ensure correct updates with block optimizations, we need to:
+        // 1. Force keep-alive into a block. This avoids its children being
+        //    collected by a parent block.
+        shouldUseBlock = true
+        // 2. Force keep-alive to always be updated, since it uses raw children.
+        patchFlag |= PatchFlags.DYNAMIC_SLOTS
+        if (__DEV__ && node.children.length > 1) {
+          context.onError(
+            createCompilerError(ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN, {
+              start: node.children[0].loc.start,
+              end: node.children[node.children.length - 1].loc.end,
+              source: ''
+            })
+          )
+        }
       }
 
-      // Portal & KeepAlive should have normal children instead of slots
-      // Portal is not a real component has dedicated handling in the renderer
-      // KeepAlive should not track its own deps so that it can be used inside
-      // Transition
-      if (isComponent && nodeType !== PORTAL && nodeType !== KEEP_ALIVE) {
+      const shouldBuildAsSlots =
+        isComponent &&
+        // Portal is not a real component has dedicated handling in the renderer
+        nodeType !== PORTAL &&
+        // explained above.
+        nodeType !== KEEP_ALIVE
+
+      if (shouldBuildAsSlots) {
         const { slots, hasDynamicSlots } = buildSlots(node, context)
         args.push(slots)
         if (hasDynamicSlots) {