feat(compiler): force dynamicSlots flag when inside v-for or v-slot
This commit is contained in:
		
							parent
							
								
									4dea23f79e
								
							
						
					
					
						commit
						c2fc7e3347
					
				@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -49,10 +49,10 @@ export function baseCompile(
 | 
			
		||||
        ? [
 | 
			
		||||
            // order is important
 | 
			
		||||
            trackVForSlotScopes,
 | 
			
		||||
            transformExpression,
 | 
			
		||||
            trackSlotScopes
 | 
			
		||||
            transformExpression
 | 
			
		||||
          ]
 | 
			
		||||
        : []),
 | 
			
		||||
      trackSlotScopes,
 | 
			
		||||
      optimizeText,
 | 
			
		||||
      transformStyle,
 | 
			
		||||
      transformSlotOutlet,
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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 }"/>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user