2019-09-28 08:29:20 +08:00
|
|
|
import {
|
|
|
|
ElementNode,
|
|
|
|
ObjectExpression,
|
|
|
|
createObjectExpression,
|
|
|
|
NodeTypes,
|
2019-09-28 10:25:32 +08:00
|
|
|
createObjectProperty,
|
|
|
|
createSimpleExpression,
|
|
|
|
createFunctionExpression,
|
|
|
|
DirectiveNode,
|
|
|
|
ElementTypes,
|
|
|
|
ExpressionNode,
|
|
|
|
Property,
|
2019-10-01 21:27:34 +08:00
|
|
|
TemplateChildNode,
|
2019-10-03 05:18:11 +08:00
|
|
|
SourceLocation,
|
|
|
|
createConditionalExpression,
|
|
|
|
ConditionalExpression,
|
|
|
|
JSChildNode,
|
|
|
|
SimpleExpressionNode
|
2019-09-28 08:29:20 +08:00
|
|
|
} from '../ast'
|
2019-09-28 12:19:24 +08:00
|
|
|
import { TransformContext, NodeTransform } from '../transform'
|
2019-09-28 08:29:20 +08:00
|
|
|
import { createCompilerError, ErrorCodes } from '../errors'
|
2019-10-03 05:18:11 +08:00
|
|
|
import { mergeExpressions, findNonEmptyDir } from '../utils'
|
2019-09-28 10:25:32 +08:00
|
|
|
|
2019-10-03 01:11:07 +08:00
|
|
|
export const isVSlot = (p: ElementNode['props'][0]): p is DirectiveNode =>
|
2019-09-28 10:25:32 +08:00
|
|
|
p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
|
2019-09-28 08:29:20 +08:00
|
|
|
|
2019-10-03 05:18:11 +08:00
|
|
|
const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
|
|
|
|
p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic
|
|
|
|
|
|
|
|
const defaultFallback = createSimpleExpression(`undefined`, false)
|
|
|
|
|
2019-09-28 12:19:24 +08:00
|
|
|
// 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 }
|
|
|
|
export const trackSlotScopes: NodeTransform = (node, context) => {
|
|
|
|
if (
|
|
|
|
node.type === NodeTypes.ELEMENT &&
|
|
|
|
(node.tagType === ElementTypes.COMPONENT ||
|
|
|
|
node.tagType === ElementTypes.TEMPLATE)
|
|
|
|
) {
|
|
|
|
const vSlot = node.props.find(isVSlot)
|
|
|
|
if (vSlot && vSlot.exp) {
|
2019-09-29 04:02:08 +08:00
|
|
|
context.addIdentifiers(vSlot.exp)
|
|
|
|
return () => {
|
|
|
|
context.removeIdentifiers(vSlot.exp!)
|
2019-09-28 12:19:24 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Instead of being a DirectiveTransform, v-slot processing is called during
|
|
|
|
// transformElement to build the slots object for a component.
|
2019-09-28 08:29:20 +08:00
|
|
|
export function buildSlots(
|
2019-09-28 10:25:32 +08:00
|
|
|
{ props, children, loc }: ElementNode,
|
2019-09-28 08:29:20 +08:00
|
|
|
context: TransformContext
|
2019-10-01 09:17:12 +08:00
|
|
|
): {
|
|
|
|
slots: ObjectExpression
|
2019-10-03 05:18:11 +08:00
|
|
|
hasDynamicSlots: boolean
|
2019-10-01 09:17:12 +08:00
|
|
|
} {
|
2019-09-28 10:25:32 +08:00
|
|
|
const slots: Property[] = []
|
2019-10-03 05:18:11 +08:00
|
|
|
let hasDynamicSlots = false
|
2019-09-28 10:25:32 +08:00
|
|
|
|
|
|
|
// 1. Check for default slot with slotProps on component itself.
|
|
|
|
// <Comp v-slot="{ prop }"/>
|
|
|
|
const explicitDefaultSlot = props.find(isVSlot)
|
|
|
|
if (explicitDefaultSlot) {
|
|
|
|
const { arg, exp, loc } = explicitDefaultSlot
|
|
|
|
if (arg) {
|
|
|
|
context.onError(
|
|
|
|
createCompilerError(ErrorCodes.X_NAMED_SLOT_ON_COMPONENT, loc)
|
|
|
|
)
|
|
|
|
}
|
2019-10-03 05:18:11 +08:00
|
|
|
slots.push(buildDefaultSlot(exp, children, loc))
|
2019-09-28 10:25:32 +08:00
|
|
|
}
|
2019-09-28 08:29:20 +08:00
|
|
|
|
2019-09-28 10:25:32 +08:00
|
|
|
// 2. Iterate through children and check for template slots
|
|
|
|
// <template v-slot:foo="{ prop }">
|
|
|
|
let hasTemplateSlots = false
|
2019-10-01 21:27:34 +08:00
|
|
|
let extraneousChild: TemplateChildNode | undefined = undefined
|
2019-09-28 10:25:32 +08:00
|
|
|
const seenSlotNames = new Set<string>()
|
|
|
|
for (let i = 0; i < children.length; i++) {
|
2019-10-03 05:18:11 +08:00
|
|
|
const slotElement = children[i]
|
2019-09-28 13:35:49 +08:00
|
|
|
let slotDir
|
2019-10-03 05:18:11 +08:00
|
|
|
|
2019-09-28 10:25:32 +08:00
|
|
|
if (
|
2019-10-03 05:18:11 +08:00
|
|
|
slotElement.type !== NodeTypes.ELEMENT ||
|
|
|
|
slotElement.tagType !== ElementTypes.TEMPLATE ||
|
|
|
|
!(slotDir = slotElement.props.find(isVSlot))
|
2019-09-28 10:25:32 +08:00
|
|
|
) {
|
2019-10-03 05:18:11 +08:00
|
|
|
// not a <template v-slot>, skip.
|
|
|
|
extraneousChild = extraneousChild || slotElement
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if (explicitDefaultSlot) {
|
|
|
|
// already has on-component default slot - this is incorrect usage.
|
|
|
|
context.onError(
|
|
|
|
createCompilerError(ErrorCodes.X_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
|
|
|
|
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/else/else-if)
|
|
|
|
let vIf
|
|
|
|
let vElse
|
|
|
|
if ((vIf = findNonEmptyDir(slotElement, 'if'))) {
|
|
|
|
hasDynamicSlots = true
|
|
|
|
slots.push(
|
|
|
|
createObjectProperty(
|
|
|
|
slotName,
|
|
|
|
createConditionalExpression(vIf.exp!, slotFunction, defaultFallback)
|
2019-09-28 13:35:49 +08:00
|
|
|
)
|
2019-10-03 05:18:11 +08:00
|
|
|
)
|
|
|
|
} else if ((vElse = findNonEmptyDir(slotElement, /^else(-if)?$/))) {
|
|
|
|
hasDynamicSlots = true
|
|
|
|
// find adjacent v-if slot
|
|
|
|
let vIfBase
|
|
|
|
let i = slots.length
|
|
|
|
while (i--) {
|
|
|
|
if (slots[i].value.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {
|
|
|
|
vIfBase = slots[i]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (vIfBase) {
|
|
|
|
// check if the v-else and the base v-if has the same slot name
|
2019-09-28 13:35:49 +08:00
|
|
|
if (
|
2019-10-03 05:18:11 +08:00
|
|
|
isStaticExp(vIfBase.key) &&
|
|
|
|
vIfBase.key.content === staticSlotName
|
2019-09-28 13:35:49 +08:00
|
|
|
) {
|
2019-10-03 05:18:11 +08:00
|
|
|
let conditional = vIfBase.value as ConditionalExpression
|
|
|
|
while (
|
|
|
|
conditional.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION
|
|
|
|
) {
|
|
|
|
conditional = conditional.alternate
|
2019-09-28 10:25:32 +08:00
|
|
|
}
|
2019-10-03 05:18:11 +08:00
|
|
|
conditional.alternate = vElse.exp
|
|
|
|
? createConditionalExpression(
|
|
|
|
vElse.exp,
|
|
|
|
slotFunction,
|
|
|
|
defaultFallback
|
|
|
|
)
|
|
|
|
: slotFunction
|
2019-10-01 09:17:12 +08:00
|
|
|
} else {
|
2019-10-03 05:18:11 +08:00
|
|
|
// not the same slot name. generate a separate property.
|
|
|
|
slots.push(
|
|
|
|
createObjectProperty(
|
|
|
|
slotName,
|
|
|
|
createConditionalExpression(
|
|
|
|
// negate baseVIf
|
|
|
|
mergeExpressions(
|
|
|
|
`!(`,
|
|
|
|
(vIfBase.value as ConditionalExpression).test,
|
|
|
|
`)`,
|
|
|
|
...(vElse.exp ? [` && (`, vElse.exp, `)`] : [])
|
|
|
|
),
|
|
|
|
slotFunction,
|
|
|
|
defaultFallback
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
2019-09-28 10:25:32 +08:00
|
|
|
}
|
2019-10-03 05:18:11 +08:00
|
|
|
} else {
|
|
|
|
context.onError(
|
|
|
|
createCompilerError(ErrorCodes.X_ELSE_NO_ADJACENT_IF, vElse.loc)
|
2019-09-28 13:35:49 +08:00
|
|
|
)
|
2019-09-28 10:25:32 +08:00
|
|
|
}
|
2019-10-03 05:18:11 +08:00
|
|
|
} else {
|
|
|
|
// check duplicate static names
|
|
|
|
if (staticSlotName) {
|
|
|
|
if (seenSlotNames.has(staticSlotName)) {
|
|
|
|
context.onError(
|
|
|
|
createCompilerError(ErrorCodes.X_DUPLICATE_SLOT_NAMES, dirLoc)
|
|
|
|
)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
seenSlotNames.add(staticSlotName)
|
|
|
|
}
|
|
|
|
slots.push(createObjectProperty(slotName, slotFunction))
|
2019-09-28 10:25:32 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-28 13:35:49 +08:00
|
|
|
if (hasTemplateSlots && extraneousChild) {
|
2019-09-28 10:25:32 +08:00
|
|
|
context.onError(
|
|
|
|
createCompilerError(
|
|
|
|
ErrorCodes.X_EXTRANEOUS_NON_SLOT_CHILDREN,
|
2019-09-28 13:35:49 +08:00
|
|
|
extraneousChild.loc
|
2019-09-28 10:25:32 +08:00
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!explicitDefaultSlot && !hasTemplateSlots) {
|
|
|
|
// implicit default slot.
|
2019-10-03 05:18:11 +08:00
|
|
|
slots.push(buildDefaultSlot(undefined, children, loc))
|
2019-09-28 10:25:32 +08:00
|
|
|
}
|
|
|
|
|
2019-10-01 09:17:12 +08:00
|
|
|
return {
|
|
|
|
slots: createObjectExpression(slots, loc),
|
2019-10-03 05:18:11 +08:00
|
|
|
hasDynamicSlots
|
2019-10-01 09:17:12 +08:00
|
|
|
}
|
2019-09-28 10:25:32 +08:00
|
|
|
}
|
|
|
|
|
2019-10-03 05:18:11 +08:00
|
|
|
function buildDefaultSlot(
|
2019-09-28 10:25:32 +08:00
|
|
|
slotProps: ExpressionNode | undefined,
|
2019-10-01 21:27:34 +08:00
|
|
|
children: TemplateChildNode[],
|
2019-09-28 10:25:32 +08:00
|
|
|
loc: SourceLocation
|
|
|
|
): Property {
|
|
|
|
return createObjectProperty(
|
2019-10-03 05:18:11 +08:00
|
|
|
createSimpleExpression(`default`, true),
|
2019-09-28 10:25:32 +08:00
|
|
|
createFunctionExpression(
|
|
|
|
slotProps,
|
|
|
|
children,
|
2019-10-02 04:48:20 +08:00
|
|
|
false,
|
2019-09-28 10:25:32 +08:00
|
|
|
children.length ? children[0].loc : loc
|
2019-10-02 00:25:13 +08:00
|
|
|
)
|
2019-09-28 10:25:32 +08:00
|
|
|
)
|
2019-09-28 08:29:20 +08:00
|
|
|
}
|