refactor: improve vSlot.ts readability

This commit is contained in:
Evan You 2019-10-02 18:03:42 -04:00
parent 35cb3700b8
commit f401ac6b88
2 changed files with 63 additions and 52 deletions

View File

@ -19,7 +19,7 @@ import {
} from '../ast' } from '../ast'
import { TransformContext, NodeTransform } from '../transform' import { TransformContext, NodeTransform } from '../transform'
import { createCompilerError, ErrorCodes } from '../errors' import { createCompilerError, ErrorCodes } from '../errors'
import { mergeExpressions, findNonEmptyDir } from '../utils' import { mergeExpressions, findDir } from '../utils'
export const isVSlot = (p: ElementNode['props'][0]): p is DirectiveNode => export const isVSlot = (p: ElementNode['props'][0]): p is DirectiveNode =>
p.type === NodeTypes.DIRECTIVE && p.name === 'slot' p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
@ -29,6 +29,9 @@ const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
const defaultFallback = createSimpleExpression(`undefined`, false) const defaultFallback = createSimpleExpression(`undefined`, false)
const hasSameName = (slot: Property, name: string): boolean =>
isStaticExp(slot.key) && slot.key.content === name
// A NodeTransform that tracks scope identifiers for scoped slots so that they // A NodeTransform that tracks scope identifiers for scoped slots so that they
// don't get prefixed by transformExpression. This transform is only applied // don't get prefixed by transformExpression. This transform is only applied
// in non-browser builds with { prefixIdentifiers: true } // in non-browser builds with { prefixIdentifiers: true }
@ -109,7 +112,7 @@ export function buildSlots(
} = slotDir } = slotDir
// check if name is dynamic. // check if name is dynamic.
let staticSlotName let staticSlotName: string | undefined
if (isStaticExp(slotName)) { if (isStaticExp(slotName)) {
staticSlotName = slotName ? slotName.content : `default` staticSlotName = slotName ? slotName.content : `default`
} else { } else {
@ -124,9 +127,9 @@ export function buildSlots(
) )
// check if this slot is conditional (v-if/else/else-if) // check if this slot is conditional (v-if/else/else-if)
let vIf let vIf: DirectiveNode | undefined
let vElse let vElse: DirectiveNode | undefined
if ((vIf = findNonEmptyDir(slotElement, 'if'))) { if ((vIf = findDir(slotElement, 'if'))) {
hasDynamicSlots = true hasDynamicSlots = true
slots.push( slots.push(
createObjectProperty( createObjectProperty(
@ -134,59 +137,66 @@ export function buildSlots(
createConditionalExpression(vIf.exp!, slotFunction, defaultFallback) createConditionalExpression(vIf.exp!, slotFunction, defaultFallback)
) )
) )
} else if ((vElse = findNonEmptyDir(slotElement, /^else(-if)?$/))) { } else if (
(vElse = findDir(slotElement, /^else(-if)?$/, true /* allow empty */))
) {
hasDynamicSlots = true hasDynamicSlots = true
// find adjacent v-if slot // find adjacent v-if slot
let vIfBase let baseIfSlot: Property | undefined
let baseIfSlotWithSameName: Property | undefined
let i = slots.length let i = slots.length
while (i--) { while (i--) {
if (slots[i].value.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) { if (slots[i].value.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {
vIfBase = slots[i] baseIfSlot = slots[i]
break if (staticSlotName && hasSameName(baseIfSlot, staticSlotName)) {
baseIfSlotWithSameName = baseIfSlot
break
}
} }
} }
if (vIfBase) { if (!baseIfSlot) {
// 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( context.onError(
createCompilerError(ErrorCodes.X_ELSE_NO_ADJACENT_IF, vElse.loc) createCompilerError(ErrorCodes.X_ELSE_NO_ADJACENT_IF, vElse.loc)
) )
continue
}
if (baseIfSlotWithSameName) {
// v-else branch has same slot name with base v-if branch
let conditional = baseIfSlotWithSameName.value as ConditionalExpression
// locate the deepest conditional in case we have nested ones
while (
conditional.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION
) {
conditional = conditional.alternate
}
// attach the v-else branch to the base v-if's conditional expression
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 base branch condition
mergeExpressions(
`!(`,
(baseIfSlot.value as ConditionalExpression).test,
`)`,
...(vElse.exp ? [` && (`, vElse.exp, `)`] : [])
),
slotFunction,
defaultFallback
)
)
)
} }
} else { } else {
// check duplicate static names // check duplicate static names

View File

@ -111,16 +111,17 @@ export function assert(condition: boolean, msg?: string) {
} }
} }
export function findNonEmptyDir( export function findDir(
node: ElementNode, node: ElementNode,
name: string | RegExp name: string | RegExp,
allowEmpty: boolean = false
): DirectiveNode | undefined { ): DirectiveNode | undefined {
for (let i = 0; i < node.props.length; i++) { for (let i = 0; i < node.props.length; i++) {
const p = node.props[i] const p = node.props[i]
if ( if (
p.type === NodeTypes.DIRECTIVE && p.type === NodeTypes.DIRECTIVE &&
p.exp && (allowEmpty || p.exp) &&
(isString(name) ? p.name === name : name.test(p.name)) p.name.match(name)
) { ) {
return p return p
} }