import { ElementNode , ObjectExpression , createObjectExpression , NodeTypes , createObjectProperty , createSimpleExpression , createFunctionExpression , DirectiveNode , ElementTypes , ExpressionNode , Property , TemplateChildNode , SourceLocation , createConditionalExpression , ConditionalExpression , SimpleExpressionNode , FunctionExpression , CallExpression , createCallExpression , createArrayExpression , SlotsExpression } from '../ast' ; import { TransformContext , NodeTransform } from '../transform' ; import { createCompilerError , ErrorCodes } from '../errors' ; import { findDir , isTemplateNode , assert , isVSlot , hasScopeRef , isStaticExp } from '../utils' ; import { CREATE_SLOTS , RENDER_LIST , WITH_CTX } from '../runtimeHelpers' ; import { parseForExpression , createForLoopParams } from './vFor' ; import { SlotFlags , slotFlagsText } from '@vue/shared' ; const defaultFallback = createSimpleExpression ( `undefined` , false ) ; export const trackSlotScopes = ( node , context ) => { if ( node . type === NodeTypes . ELEMENT && ( node . tagType === ElementTypes . COMPONENT || node . tagType === ElementTypes . TEMPLATE ) ) { 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 -- ; } ; } } } ; export const trackVForSlotScopes = ( 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 ) ; } ; } } } ; export type SlotFnBuilder = ( slotProps : ExpressionNode | undefined , slotChildren : TemplateChildNode [ ] , loc : SourceLocation ) => FunctionExpression ; const buildClientSlotFn = ( props , children , loc ) => createFunctionExpression ( props , children , false , true , children . length ? children [ 0 ] . loc : loc ) ; export function buildSlots ( node , context , buildSlotFn = buildClientSlotFn ) { context . helper ( WITH_CTX ) ; const { children , loc } = node ; const slotsProperties = [ ] ; const dynamicSlots = [ ] ; let hasDynamicSlots = context . scopes . vSlot > 0 || context . scopes . vFor > 0 ; if ( ! __BROWSER__ && ! context . ssr && context . prefixIdentifiers ) { hasDynamicSlots = hasScopeRef ( node , context . identifiers ) ; } const onComponentSlot = findDir ( node , 'slot' , true ) ; if ( onComponentSlot ) { const { arg , exp } = onComponentSlot ; if ( arg && ! isStaticExp ( arg ) ) { hasDynamicSlots = true ; } slotsProperties . push ( createObjectProperty ( arg || createSimpleExpression ( 'default' , true ) , buildSlotFn ( exp , children , loc ) ) ) ; } let hasTemplateSlots = false ; let hasNamedDefaultSlot = false ; const implicitDefaultChildren = [ ] ; const seenSlotNames = new Set < string > ( ) ; for ( let i = 0 ; i < children . length ; i ++ ) { const slotElement = children [ i ] ; let slotDir ; if ( ! isTemplateNode ( slotElement ) || ! ( slotDir = findDir ( slotElement , 'slot' , true ) ) ) { if ( slotElement . type !== NodeTypes . COMMENT ) { implicitDefaultChildren . push ( slotElement ) ; } continue ; } if ( onComponentSlot ) { context . onError ( createCompilerError ( ErrorCodes . X_V_SLOT_MIXED_SLOT_USAGE , slotDir . loc ) ) ; break ; } hasTemplateSlots = true ; const { children : slotChildren , loc : slotLoc } = slotElement ; const { arg : slotName = createSimpleExpression ( `default` , true ) , exp : slotProps , loc : dirLoc } = slotDir ; let staticSlotName ; if ( isStaticExp ( slotName ) ) { staticSlotName = slotName ? slotName . content : `default` ; } else { hasDynamicSlots = true ; } const slotFunction = buildSlotFn ( slotProps , slotChildren , slotLoc ) ; let vIf ; let vElse ; let vFor ; if ( ( vIf = findDir ( slotElement , 'if' ) ) ) { hasDynamicSlots = true ; dynamicSlots . push ( createConditionalExpression ( vIf . exp ! , buildDynamicSlot ( slotName , slotFunction ) , defaultFallback ) ) ; } else if ( ( vElse = findDir ( slotElement , /^else(-if)?$/ , true ) ) ) { let j = i ; let prev ; while ( j -- ) { prev = children [ j ] ; if ( prev . type !== NodeTypes . COMMENT ) { break ; } } if ( prev && isTemplateNode ( prev ) && findDir ( prev , 'if' ) ) { children . splice ( i , 1 ) ; i -- ; __TEST__ && assert ( dynamicSlots . length > 0 ) ; let conditional = dynamicSlots [ dynamicSlots . length - 1 ] as ConditionalExpression ; while ( conditional . alternate . type === NodeTypes . JS_CONDITIONAL_EXPRESSION ) { conditional = conditional . alternate ; } conditional . alternate = vElse . exp ? createConditionalExpression ( vElse . exp , buildDynamicSlot ( slotName , slotFunction ) , defaultFallback ) : buildDynamicSlot ( slotName , slotFunction ) ; } else { context . onError ( createCompilerError ( ErrorCodes . X_V_ELSE_NO_ADJACENT_IF , vElse . loc ) ) ; } } else if ( ( vFor = findDir ( slotElement , 'for' ) ) ) { hasDynamicSlots = true ; const parseResult = vFor . parseResult || parseForExpression ( vFor . exp as SimpleExpressionNode , context ) ; if ( parseResult ) { dynamicSlots . push ( createCallExpression ( context . helper ( RENDER_LIST ) , [ parseResult . source , createFunctionExpression ( createForLoopParams ( parseResult ) , buildDynamicSlot ( slotName , slotFunction ) , true ) ] ) ) ; } else { context . onError ( createCompilerError ( ErrorCodes . X_V_FOR_MALFORMED_EXPRESSION , vFor . loc ) ) ; } } else { if ( staticSlotName ) { if ( seenSlotNames . has ( staticSlotName ) ) { context . onError ( createCompilerError ( ErrorCodes . X_V_SLOT_DUPLICATE_SLOT_NAMES , dirLoc ) ) ; continue ; } seenSlotNames . add ( staticSlotName ) ; if ( staticSlotName === 'default' ) { hasNamedDefaultSlot = true ; } } slotsProperties . push ( createObjectProperty ( slotName , slotFunction ) ) ; } } if ( ! onComponentSlot ) { const buildDefaultSlotProperty = ( props , children ) => { const fn = buildSlotFn ( props , children , loc ) ; if ( __COMPAT__ && context . compatConfig ) { fn . isNonScopedSlot = true ; } return createObjectProperty ( `default` , fn ) ; } ; if ( ! hasTemplateSlots ) { slotsProperties . push ( buildDefaultSlotProperty ( undefined , children ) ) ; } else if ( implicitDefaultChildren . length && implicitDefaultChildren . some ( node => isNonWhitespaceContent ( node ) ) ) { if ( hasNamedDefaultSlot ) { context . onError ( createCompilerError ( ErrorCodes . X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN , implicitDefaultChildren [ 0 ] . loc ) ) ; } else { slotsProperties . push ( buildDefaultSlotProperty ( undefined , implicitDefaultChildren ) ) ; } } } const slotFlag = hasDynamicSlots ? SlotFlags . DYNAMIC : hasForwardedSlots ( node . children ) ? SlotFlags . FORWARDED : SlotFlags . STABLE ; let slots = createObjectExpression ( slotsProperties . concat ( createObjectProperty ( `_` , createSimpleExpression ( slotFlag + ( __DEV__ ? ` /* ${ slotFlagsText [ slotFlag ] } */` : `` ) , false ) ) ) , loc ) as SlotsExpression ; if ( dynamicSlots . length ) { slots = createCallExpression ( context . helper ( CREATE_SLOTS ) , [ slots , createArrayExpression ( dynamicSlots ) ] ) as SlotsExpression ; } return { slots , hasDynamicSlots } ; } function buildDynamicSlot ( name , fn ) { return createObjectExpression ( [ createObjectProperty ( `name` , name ) , createObjectProperty ( `fn` , fn ) ] ) ; } function hasForwardedSlots ( children ) { for ( let i = 0 ; i < children . length ; i ++ ) { const child = children [ i ] ; switch ( child . type ) { case NodeTypes . ELEMENT : if ( child . tagType === ElementTypes . SLOT || hasForwardedSlots ( child . children ) ) { return true ; } break ; case NodeTypes . IF : if ( hasForwardedSlots ( child . branches ) ) return true ; break ; case NodeTypes . IF_BRANCH : case NodeTypes . FOR : if ( hasForwardedSlots ( child . children ) ) return true ; break ; default : break ; } } return false ; } function isNonWhitespaceContent ( node ) { if ( node . type !== NodeTypes . TEXT && node . type !== NodeTypes . TEXT_CALL ) return true ; return node . type === NodeTypes . TEXT ? ! ! node . content . trim ( ) : isNonWhitespaceContent ( node . content ) ; }