diff --git a/packages/compiler-core/__tests__/transforms/vIf.spec.ts b/packages/compiler-core/__tests__/transforms/vIf.spec.ts index 07545152..1010e914 100644 --- a/packages/compiler-core/__tests__/transforms/vIf.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vIf.spec.ts @@ -233,7 +233,7 @@ describe('compiler: v-if', () => { }) expect(onError.mock.calls[0]).toMatchObject([ { - code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, + code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, loc: node1.loc } ]) @@ -245,7 +245,7 @@ describe('compiler: v-if', () => { ) expect(onError.mock.calls[1]).toMatchObject([ { - code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, + code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, loc: node2.loc } ]) @@ -257,7 +257,7 @@ describe('compiler: v-if', () => { ) expect(onError.mock.calls[2]).toMatchObject([ { - code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, + code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, loc: node3.loc } ]) diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts index 4ec54b74..e64183dd 100644 --- a/packages/compiler-core/src/errors.ts +++ b/packages/compiler-core/src/errors.ts @@ -63,7 +63,6 @@ export const enum ErrorCodes { // transform errors X_IF_NO_EXPRESSION, - X_ELSE_IF_NO_ADJACENT_IF, X_ELSE_NO_ADJACENT_IF, X_FOR_NO_EXPRESSION, X_FOR_MALFORMED_EXPRESSION, @@ -140,8 +139,7 @@ export const errorMessages: { [code: number]: string } = { // transform errors [ErrorCodes.X_IF_NO_EXPRESSION]: `v-if/v-else-if is missing expression.`, - [ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF]: `v-else-if has no adjacent v-if.`, - [ErrorCodes.X_ELSE_NO_ADJACENT_IF]: `v-else has no adjacent v-if.`, + [ErrorCodes.X_ELSE_NO_ADJACENT_IF]: `v-else/v-else-if has no adjacent v-if.`, [ErrorCodes.X_FOR_NO_EXPRESSION]: `v-for is missing expression.`, [ErrorCodes.X_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`, [ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`, diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index 7931f019..d5585dd8 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -16,6 +16,7 @@ import { isString, isArray } from '@vue/shared' import { CompilerError, defaultOnError } from './errors' import { TO_STRING, COMMENT, CREATE_VNODE, FRAGMENT } from './runtimeConstants' import { createBlockExpression } from './utils' +import { isVSlot } from './transforms/vSlot' // There are two types of transforms: // @@ -311,6 +312,11 @@ export function createStructuralDirectiveTransform( return (node, context) => { if (node.type === NodeTypes.ELEMENT) { const { props } = node + // structural directive transforms are not concerned with slots + // as they are handled separately in vSlot.ts + if (node.tagType === ElementTypes.TEMPLATE && props.some(isVSlot)) { + return + } const exitFns = [] for (let i = 0; i < props.length; i++) { const prop = props[i] diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 121e0fbc..0f1c051d 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -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) { diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts index 2a8a6ff2..8aec71ec 100644 --- a/packages/compiler-core/src/transforms/vFor.ts +++ b/packages/compiler-core/src/transforms/vFor.ts @@ -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 diff --git a/packages/compiler-core/src/transforms/vIf.ts b/packages/compiler-core/src/transforms/vIf.ts index 24760e47..23276d37 100644 --- a/packages/compiler-core/src/transforms/vIf.ts +++ b/packages/compiler-core/src/transforms/vIf.ts @@ -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 diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts index 989ccce7..4f6e0683 100644 --- a/packages/compiler-core/src/transforms/vSlot.ts +++ b/packages/compiler-core/src/transforms/vSlot.ts @@ -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. // @@ -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() 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