feat(compiler): handle conditional v-slot
This commit is contained in:
@@ -11,15 +11,24 @@ import {
|
||||
ExpressionNode,
|
||||
Property,
|
||||
TemplateChildNode,
|
||||
SourceLocation
|
||||
SourceLocation,
|
||||
createConditionalExpression,
|
||||
ConditionalExpression,
|
||||
JSChildNode,
|
||||
SimpleExpressionNode
|
||||
} from '../ast'
|
||||
import { TransformContext, NodeTransform } from '../transform'
|
||||
import { createCompilerError, ErrorCodes } from '../errors'
|
||||
import { isString } from '@vue/shared'
|
||||
import { mergeExpressions, findNonEmptyDir } from '../utils'
|
||||
|
||||
export const isVSlot = (p: ElementNode['props'][0]): p is DirectiveNode =>
|
||||
p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
|
||||
|
||||
const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
|
||||
p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic
|
||||
|
||||
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 }
|
||||
@@ -46,10 +55,10 @@ export function buildSlots(
|
||||
context: TransformContext
|
||||
): {
|
||||
slots: ObjectExpression
|
||||
hasDynamicSlotName: boolean
|
||||
hasDynamicSlots: boolean
|
||||
} {
|
||||
const slots: Property[] = []
|
||||
let hasDynamicSlotName = false
|
||||
let hasDynamicSlots = false
|
||||
|
||||
// 1. Check for default slot with slotProps on component itself.
|
||||
// <Comp v-slot="{ prop }"/>
|
||||
@@ -61,7 +70,7 @@ export function buildSlots(
|
||||
createCompilerError(ErrorCodes.X_NAMED_SLOT_ON_COMPONENT, loc)
|
||||
)
|
||||
}
|
||||
slots.push(buildSlot(`default`, exp, children, loc))
|
||||
slots.push(buildDefaultSlot(exp, children, loc))
|
||||
}
|
||||
|
||||
// 2. Iterate through children and check for template slots
|
||||
@@ -70,45 +79,127 @@ export function buildSlots(
|
||||
let extraneousChild: TemplateChildNode | undefined = undefined
|
||||
const seenSlotNames = new Set<string>()
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i]
|
||||
const slotElement = children[i]
|
||||
let slotDir
|
||||
|
||||
if (
|
||||
child.type === NodeTypes.ELEMENT &&
|
||||
child.tagType === ElementTypes.TEMPLATE &&
|
||||
(slotDir = child.props.find(isVSlot))
|
||||
slotElement.type !== NodeTypes.ELEMENT ||
|
||||
slotElement.tagType !== ElementTypes.TEMPLATE ||
|
||||
!(slotDir = slotElement.props.find(isVSlot))
|
||||
) {
|
||||
hasTemplateSlots = true
|
||||
const { children, loc: nodeLoc } = child
|
||||
const { arg: slotName, exp: slotProps, loc: dirLoc } = slotDir
|
||||
if (explicitDefaultSlot) {
|
||||
// already has on-component default slot - this is incorrect usage.
|
||||
context.onError(
|
||||
createCompilerError(ErrorCodes.X_MIXED_SLOT_USAGE, dirLoc)
|
||||
// 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)
|
||||
)
|
||||
break
|
||||
} else {
|
||||
if (
|
||||
!slotName ||
|
||||
(slotName.type === NodeTypes.SIMPLE_EXPRESSION && slotName.isStatic)
|
||||
) {
|
||||
// check duplicate slot names
|
||||
const name = slotName ? slotName.content : `default`
|
||||
if (seenSlotNames.has(name)) {
|
||||
context.onError(
|
||||
createCompilerError(ErrorCodes.X_DUPLICATE_SLOT_NAMES, dirLoc)
|
||||
)
|
||||
continue
|
||||
}
|
||||
seenSlotNames.add(name)
|
||||
} else {
|
||||
hasDynamicSlotName = true
|
||||
)
|
||||
} 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
|
||||
}
|
||||
slots.push(
|
||||
buildSlot(slotName || `default`, slotProps, children, nodeLoc)
|
||||
}
|
||||
if (vIfBase) {
|
||||
// check if the v-else and the base v-if has the same slot name
|
||||
if (
|
||||
isStaticExp(vIfBase.key) &&
|
||||
vIfBase.key.content === staticSlotName
|
||||
) {
|
||||
let conditional = vIfBase.value as ConditionalExpression
|
||||
while (
|
||||
conditional.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION
|
||||
) {
|
||||
conditional = conditional.alternate
|
||||
}
|
||||
conditional.alternate = vElse.exp
|
||||
? createConditionalExpression(
|
||||
vElse.exp,
|
||||
slotFunction,
|
||||
defaultFallback
|
||||
)
|
||||
: slotFunction
|
||||
} else {
|
||||
// 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
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
context.onError(
|
||||
createCompilerError(ErrorCodes.X_ELSE_NO_ADJACENT_IF, vElse.loc)
|
||||
)
|
||||
}
|
||||
} else if (!extraneousChild) {
|
||||
extraneousChild = child
|
||||
} 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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,23 +214,22 @@ export function buildSlots(
|
||||
|
||||
if (!explicitDefaultSlot && !hasTemplateSlots) {
|
||||
// implicit default slot.
|
||||
slots.push(buildSlot(`default`, undefined, children, loc))
|
||||
slots.push(buildDefaultSlot(undefined, children, loc))
|
||||
}
|
||||
|
||||
return {
|
||||
slots: createObjectExpression(slots, loc),
|
||||
hasDynamicSlotName
|
||||
hasDynamicSlots
|
||||
}
|
||||
}
|
||||
|
||||
function buildSlot(
|
||||
name: string | ExpressionNode,
|
||||
function buildDefaultSlot(
|
||||
slotProps: ExpressionNode | undefined,
|
||||
children: TemplateChildNode[],
|
||||
loc: SourceLocation
|
||||
): Property {
|
||||
return createObjectProperty(
|
||||
isString(name) ? createSimpleExpression(name, true, loc) : name,
|
||||
createSimpleExpression(`default`, true),
|
||||
createFunctionExpression(
|
||||
slotProps,
|
||||
children,
|
||||
|
||||
Reference in New Issue
Block a user