]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler): force dynamicSlots flag when inside v-for or v-slot
authorEvan You <yyx990803@gmail.com>
Thu, 3 Oct 2019 20:27:46 +0000 (16:27 -0400)
committerEvan You <yyx990803@gmail.com>
Thu, 3 Oct 2019 20:27:46 +0000 (16:27 -0400)
packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
packages/compiler-core/__tests__/transforms/vSlot.spec.ts
packages/compiler-core/src/index.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/vFor.ts
packages/compiler-core/src/transforms/vSlot.ts

index 6d5193d53b355713a4b04d34c1addbc239de3ee4..8bbb14f28eb7a5719bfb17c8e347394ce6a8576c 100644 (file)
@@ -159,7 +159,7 @@ return function render() {
       createVNode(_component_Inner, null, {
         default: ({ bar }) => [toString(foo), toString(bar), toString(_ctx.baz)],
         _compiled: true
-      }),
+      }, 256 /* DYNAMIC_SLOTS */),
       toString(foo),
       toString(_ctx.bar),
       toString(_ctx.baz)
index 5dc22d4c4e1453a633aa08ea6477d879d31871ef..1b556424659394be9c8b200d04a0b26e34faa9ba 100644 (file)
@@ -5,7 +5,8 @@ import {
   generate,
   ElementNode,
   NodeTypes,
-  ErrorCodes
+  ErrorCodes,
+  ForNode
 } from '../../src'
 import { transformElement } from '../../src/transforms/transformElement'
 import { transformOn } from '../../src/transforms/vOn'
@@ -17,15 +18,20 @@ import {
 } from '../../src/transforms/vSlot'
 import { CREATE_SLOTS, RENDER_LIST } from '../../src/runtimeConstants'
 import { createObjectMatcher } from '../testUtils'
-import { PatchFlags } from '@vue/shared'
+import { PatchFlags, PatchFlagNames } from '@vue/shared'
+import { transformFor } from '../../src/transforms/vFor'
+import { transformIf } from '../../src/transforms/vIf'
 
 function parseWithSlots(template: string, options: CompilerOptions = {}) {
   const ast = parse(template)
   transform(ast, {
     nodeTransforms: [
+      transformIf,
+      transformFor,
       ...(options.prefixIdentifiers
-        ? [trackVForSlotScopes, transformExpression, trackSlotScopes]
+        ? [trackVForSlotScopes, transformExpression]
         : []),
+      trackSlotScopes,
       transformElement
     ],
     directiveTransforms: {
@@ -36,7 +42,10 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
   })
   return {
     root: ast,
-    slots: (ast.children[0] as ElementNode).codegenNode!.arguments[2]
+    slots:
+      ast.children[0].type === NodeTypes.ELEMENT
+        ? ast.children[0].codegenNode!.arguments[2]
+        : null
   }
 }
 
@@ -295,7 +304,12 @@ describe('compiler: transform component slots', () => {
                         }
                       ]
                     }
-                  })
+                  }),
+                  // nested slot should be forced dynamic, since scope variables
+                  // are not tracked as dependencies of the slot.
+                  `${PatchFlags.DYNAMIC_SLOTS} /* ${
+                    PatchFlagNames[PatchFlags.DYNAMIC_SLOTS]
+                  } */`
                 ]
               }
             },
@@ -325,6 +339,18 @@ describe('compiler: transform component slots', () => {
     expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
   })
 
+  test('should force dynamic when inside v-for', () => {
+    const { root } = parseWithSlots(
+      `<div v-for="i in list">
+        <Comp v-slot="bar">foo</Comp>
+      </div>`
+    )
+    const div = ((root.children[0] as ForNode).children[0] as ElementNode)
+      .codegenNode as any
+    const comp = div.arguments[2][0]
+    expect(comp.codegenNode.arguments[3]).toMatch(PatchFlags.DYNAMIC_SLOTS + '')
+  })
+
   test('named slot with v-if', () => {
     const { root, slots } = parseWithSlots(
       `<Comp>
index eb23a9e02f64ca17e7edd9d4206c06496fbbc900..f126512fef1f50c730ee4a9fdb1fea7637fc6686 100644 (file)
@@ -49,10 +49,10 @@ export function baseCompile(
         ? [
             // order is important
             trackVForSlotScopes,
-            transformExpression,
-            trackSlotScopes
+            transformExpression
           ]
         : []),
+      trackSlotScopes,
       optimizeText,
       transformStyle,
       transformSlotOutlet,
index 6d1bb90ea903519a840633020b2dd0fe29abf3a1..370d4f461df66e78d432bc330eb4a0ea9a77be1c 100644 (file)
@@ -59,6 +59,12 @@ export interface TransformContext extends Required<TransformOptions> {
   statements: Set<string>
   hoists: JSChildNode[]
   identifiers: { [name: string]: number | undefined }
+  scopes: {
+    vFor: number
+    vSlot: number
+    vPre: number
+    vOnce: number
+  }
   parent: ParentNode | null
   childIndex: number
   currentNode: RootNode | TemplateChildNode | null
@@ -86,6 +92,12 @@ function createTransformContext(
     statements: new Set(),
     hoists: [],
     identifiers: {},
+    scopes: {
+      vFor: 0,
+      vSlot: 0,
+      vPre: 0,
+      vOnce: 0
+    },
     prefixIdentifiers,
     nodeTransforms,
     directiveTransforms,
index c54e957852b4e406ead0b8a931e2a788607e2ef4..30fbf24d6d0f88dfe13601af9d3bce5559427c9c 100644 (file)
@@ -46,7 +46,7 @@ export const transformFor = createStructuralDirectiveTransform(
       )
 
       if (parseResult) {
-        const { helper, addIdentifiers, removeIdentifiers } = context
+        const { helper, addIdentifiers, removeIdentifiers, scopes } = context
         const { source, value, key, index } = parseResult
 
         // create the loop render function expression now, and add the
@@ -79,6 +79,8 @@ export const transformFor = createStructuralDirectiveTransform(
           codegenNode
         })
 
+        // bookkeeping
+        scopes.vFor++
         if (!__BROWSER__ && context.prefixIdentifiers) {
           // scope management
           // inject identifiers to context
@@ -88,6 +90,7 @@ export const transformFor = createStructuralDirectiveTransform(
         }
 
         return () => {
+          scopes.vFor--
           if (!__BROWSER__ && context.prefixIdentifiers) {
             value && removeIdentifiers(value)
             key && removeIdentifiers(key)
index f68d08df289ba36e3778a4b23d14f8e2d60a889e..7bd5cda039a13fd7fd9b73a568fb6931de0bc0a8 100644 (file)
@@ -32,22 +32,33 @@ const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
 
 const defaultFallback = createSimpleExpression(`undefined`, false)
 
-// A NodeTransform that tracks scope identifiers for scoped slots so that they
-// don't get prefixed by transformExpression. This transform is only applied
-// in non-browser builds with { prefixIdentifiers: true }
+// A NodeTransform that:
+// 1. Tracks scope identifiers for scoped slots so that they don't get prefixed
+//    by transformExpression. This is only applied in non-browser builds with
+//    { prefixIdentifiers: true }.
+// 2. Track v-slot depths so that we know a slot is inside another slot.
+//    Note the exit callback is executed before buildSlots() on the same node,
+//    so only nested slots see positive numbers.
 export const trackSlotScopes: NodeTransform = (node, context) => {
   if (
     node.type === NodeTypes.ELEMENT &&
     (node.tagType === ElementTypes.COMPONENT ||
       node.tagType === ElementTypes.TEMPLATE)
   ) {
+    // We are only checking non-empty v-slot here
+    // since we only care about slots that introduce scope variables.
     const vSlot = findDir(node, 'slot')
     if (vSlot) {
-      const { addIdentifiers, removeIdentifiers } = context
       const slotProps = vSlot.exp
-      slotProps && addIdentifiers(slotProps)
+      if (!__BROWSER__ && context.prefixIdentifiers) {
+        slotProps && context.addIdentifiers(slotProps)
+      }
+      context.scopes.vSlot++
       return () => {
-        slotProps && removeIdentifiers(slotProps)
+        if (!__BROWSER__ && context.prefixIdentifiers) {
+          slotProps && context.removeIdentifiers(slotProps)
+        }
+        context.scopes.vSlot--
       }
     }
   }
@@ -94,7 +105,12 @@ export function buildSlots(
   const { children, loc } = node
   const slotsProperties: Property[] = []
   const dynamicSlots: (ConditionalExpression | CallExpression)[] = []
-  let hasDynamicSlots = false
+
+  // If the slot is inside a v-for or another v-slot, force it to be dynamic
+  // since it likely uses a scope variable.
+  // TODO: This can be further optimized to only make it dynamic when the slot
+  // actually uses the scope variables.
+  let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0
 
   // 1. Check for default slot with slotProps on component itself.
   //    <Comp v-slot="{ prop }"/>