import { ElementNode, ObjectExpression, createObjectExpression, NodeTypes, createObjectProperty, createSimpleExpression, createFunctionExpression, DirectiveNode, ElementTypes, ExpressionNode, Property, TemplateChildNode, SourceLocation, createConditionalExpression, ConditionalExpression, JSChildNode, SimpleExpressionNode, FunctionExpression, CallExpression, createCallExpression, createArrayExpression } from '../ast' import { TransformContext, NodeTransform } from '../transform' import { createCompilerError, ErrorCodes } from '../errors' import { findDir, isTemplateNode, assert, isVSlot } from '../utils' import { CREATE_SLOTS, RENDER_LIST } from '../runtimeHelpers' import { parseForExpression, createForLoopParams } from './vFor' const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode => p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic const defaultFallback = createSimpleExpression(`undefined`, false) // 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 slotProps = vSlot.exp if (!__BROWSER__ && context.prefixIdentifiers) { slotProps && context.addIdentifiers(slotProps) } context.scopes.vSlot++ return () => { if (!__BROWSER__ && context.prefixIdentifiers) { slotProps && context.removeIdentifiers(slotProps) } context.scopes.vSlot-- } } } } // A NodeTransform that tracks scope identifiers for scoped slots with v-for. // This transform is only applied in non-browser builds with { prefixIdentifiers: true } export const trackVForSlotScopes: NodeTransform = (node, context) => { let vFor if ( isTemplateNode(node) && node.props.some(isVSlot) && (vFor = findDir(node, 'for')) ) { const result = (vFor.parseResult = parseForExpression( vFor.exp as SimpleExpressionNode, context )) if (result) { const { value, key, index } = result const { addIdentifiers, removeIdentifiers } = context value && addIdentifiers(value) key && addIdentifiers(key) index && addIdentifiers(index) return () => { value && removeIdentifiers(value) key && removeIdentifiers(key) index && removeIdentifiers(index) } } } } // Instead of being a DirectiveTransform, v-slot processing is called during // transformElement to build the slots object for a component. export function buildSlots( node: ElementNode, context: TransformContext ): { slots: ObjectExpression | CallExpression hasDynamicSlots: boolean } { const { children, loc } = node const slotsProperties: Property[] = [] const dynamicSlots: (ConditionalExpression | CallExpression)[] = [] // 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 > 1 || context.scopes.vFor > 0 // 1. Check for default slot with slotProps on component itself. // const explicitDefaultSlot = findDir(node, 'slot', true) if (explicitDefaultSlot) { const { arg, exp, loc } = explicitDefaultSlot if (arg) { context.onError( createCompilerError(ErrorCodes.X_V_SLOT_NAMED_SLOT_ON_COMPONENT, loc) ) } slotsProperties.push(buildDefaultSlot(exp, children, loc)) } // 2. Iterate through children and check for template slots //