From f401ac6b887a0f7bfd93454e3131ac275ed8f657 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 2 Oct 2019 18:03:42 -0400 Subject: [PATCH] refactor: improve vSlot.ts readability --- .../compiler-core/src/transforms/vSlot.ts | 106 ++++++++++-------- packages/compiler-core/src/utils.ts | 9 +- 2 files changed, 63 insertions(+), 52 deletions(-) diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts index 4f6e0683..7075411a 100644 --- a/packages/compiler-core/src/transforms/vSlot.ts +++ b/packages/compiler-core/src/transforms/vSlot.ts @@ -19,7 +19,7 @@ import { } from '../ast' import { TransformContext, NodeTransform } from '../transform' 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 => p.type === NodeTypes.DIRECTIVE && p.name === 'slot' @@ -29,6 +29,9 @@ const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode => 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 // don't get prefixed by transformExpression. This transform is only applied // in non-browser builds with { prefixIdentifiers: true } @@ -109,7 +112,7 @@ export function buildSlots( } = slotDir // check if name is dynamic. - let staticSlotName + let staticSlotName: string | undefined if (isStaticExp(slotName)) { staticSlotName = slotName ? slotName.content : `default` } else { @@ -124,9 +127,9 @@ export function buildSlots( ) // check if this slot is conditional (v-if/else/else-if) - let vIf - let vElse - if ((vIf = findNonEmptyDir(slotElement, 'if'))) { + let vIf: DirectiveNode | undefined + let vElse: DirectiveNode | undefined + if ((vIf = findDir(slotElement, 'if'))) { hasDynamicSlots = true slots.push( createObjectProperty( @@ -134,59 +137,66 @@ export function buildSlots( createConditionalExpression(vIf.exp!, slotFunction, defaultFallback) ) ) - } else if ((vElse = findNonEmptyDir(slotElement, /^else(-if)?$/))) { + } else if ( + (vElse = findDir(slotElement, /^else(-if)?$/, true /* allow empty */)) + ) { hasDynamicSlots = true + // find adjacent v-if slot - let vIfBase + let baseIfSlot: Property | undefined + let baseIfSlotWithSameName: Property | undefined let i = slots.length while (i--) { if (slots[i].value.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) { - vIfBase = slots[i] - break + baseIfSlot = slots[i] + if (staticSlotName && hasSameName(baseIfSlot, staticSlotName)) { + baseIfSlotWithSameName = baseIfSlot + break + } } } - 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 { + if (!baseIfSlot) { context.onError( 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 { // check duplicate static names diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts index 252c4101..2f661c7f 100644 --- a/packages/compiler-core/src/utils.ts +++ b/packages/compiler-core/src/utils.ts @@ -111,16 +111,17 @@ export function assert(condition: boolean, msg?: string) { } } -export function findNonEmptyDir( +export function findDir( node: ElementNode, - name: string | RegExp + name: string | RegExp, + allowEmpty: boolean = false ): DirectiveNode | undefined { for (let i = 0; i < node.props.length; i++) { const p = node.props[i] if ( p.type === NodeTypes.DIRECTIVE && - p.exp && - (isString(name) ? p.name === name : name.test(p.name)) + (allowEmpty || p.exp) && + p.name.match(name) ) { return p }