feat(compiler): handle conditional v-slot

This commit is contained in:
Evan You
2019-10-02 17:18:11 -04:00
parent e90b83600a
commit 3d14265102
9 changed files with 187 additions and 62 deletions

View File

@@ -87,9 +87,9 @@ export const transformElement: NodeTransform = (node, context) => {
args.push(`null`)
}
if (isComponent) {
const { slots, hasDynamicSlotName } = buildSlots(node, context)
const { slots, hasDynamicSlots } = buildSlots(node, context)
args.push(slots)
if (hasDynamicSlotName) {
if (hasDynamicSlots) {
patchFlag |= PatchFlags.DYNAMIC_SLOTS
}
} else if (node.children.length === 1) {

View File

@@ -47,7 +47,7 @@ export const transformFor = createStructuralDirectiveTransform(
// create the loop render function expression now, and add the
// iterator on exit after all children have been traversed
const renderExp = createCallExpression(helper(RENDER_LIST), [source])
const keyProp = findProp(node.props, `key`)
const keyProp = findProp(node, `key`)
const fragmentFlag = keyProp
? PatchFlags.KEYED_FRAGMENT
: PatchFlags.UNKEYED_FRAGMENT

View File

@@ -114,12 +114,7 @@ export const transformIf = createStructuralDirectiveTransform(
}
} else {
context.onError(
createCompilerError(
dir.name === 'else'
? ErrorCodes.X_ELSE_NO_ADJACENT_IF
: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF,
node.loc
)
createCompilerError(ErrorCodes.X_ELSE_NO_ADJACENT_IF, node.loc)
)
}
break

View File

@@ -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,