| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- 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.
- // <Comp v-slot="{ prop }"/>
- 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
- // <template v-slot:foo="{ prop }">
- let hasTemplateSlots = false
- let extraneousChild: TemplateChildNode | undefined = undefined
- 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))
- ) {
- // not a <template v-slot>, skip.
- if (slotElement.type !== NodeTypes.COMMENT && !extraneousChild) {
- extraneousChild = slotElement
- }
- continue
- }
- if (explicitDefaultSlot) {
- // already has on-component default slot - this is incorrect usage.
- 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
- // check if name is dynamic.
- let staticSlotName: string | undefined
- if (isStaticExp(slotName)) {
- staticSlotName = slotName ? slotName.content : `default`
- } else {
- hasDynamicSlots = true
- }
- const slotFunction = createFunctionExpression(
- slotProps,
- slotChildren,
- false,
- slotChildren.length ? slotChildren[0].loc : slotLoc
- )
- // check if this slot is conditional (v-if/v-for)
- let vIf: DirectiveNode | undefined
- let vElse: DirectiveNode | undefined
- let vFor: DirectiveNode | undefined
- if ((vIf = findDir(slotElement, 'if'))) {
- hasDynamicSlots = true
- dynamicSlots.push(
- createConditionalExpression(
- vIf.exp!,
- buildDynamicSlot(slotName, slotFunction),
- defaultFallback
- )
- )
- } else if (
- (vElse = findDir(slotElement, /^else(-if)?$/, true /* allowEmpty */))
- ) {
- // find adjacent v-if
- let j = i
- let prev
- while (j--) {
- prev = children[j]
- if (prev.type !== NodeTypes.COMMENT) {
- break
- }
- }
- if (prev && isTemplateNode(prev) && findDir(prev, 'if')) {
- // remove node
- children.splice(i, 1)
- i--
- __DEV__ && assert(dynamicSlots.length > 0)
- // attach this slot to previous conditional
- 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) {
- // Render the dynamic slots as an array and add it to the createSlot()
- // args. The runtime knows how to handle it appropriately.
- 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 {
- // check duplicate static names
- if (staticSlotName) {
- if (seenSlotNames.has(staticSlotName)) {
- context.onError(
- createCompilerError(
- ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES,
- dirLoc
- )
- )
- continue
- }
- seenSlotNames.add(staticSlotName)
- }
- slotsProperties.push(createObjectProperty(slotName, slotFunction))
- }
- }
- if (hasTemplateSlots && extraneousChild) {
- context.onError(
- createCompilerError(
- ErrorCodes.X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN,
- extraneousChild.loc
- )
- )
- }
- if (!explicitDefaultSlot && !hasTemplateSlots) {
- // implicit default slot.
- slotsProperties.push(buildDefaultSlot(undefined, children, loc))
- }
- let slots: ObjectExpression | CallExpression = createObjectExpression(
- slotsProperties.concat(
- createObjectProperty(`_compiled`, createSimpleExpression(`true`, false))
- ),
- loc
- )
- if (dynamicSlots.length) {
- slots = createCallExpression(context.helper(CREATE_SLOTS), [
- slots,
- createArrayExpression(dynamicSlots)
- ])
- }
- return {
- slots,
- hasDynamicSlots
- }
- }
- function buildDefaultSlot(
- slotProps: ExpressionNode | undefined,
- children: TemplateChildNode[],
- loc: SourceLocation
- ): Property {
- return createObjectProperty(
- `default`,
- createFunctionExpression(
- slotProps,
- children,
- false,
- children.length ? children[0].loc : loc
- )
- )
- }
- function buildDynamicSlot(
- name: ExpressionNode,
- fn: FunctionExpression
- ): ObjectExpression {
- return createObjectExpression([
- createObjectProperty(`name`, name),
- createObjectProperty(`fn`, fn)
- ])
- }
|