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 , 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,
diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts
index 23eabba0..252c4101 100644
--- a/packages/compiler-core/src/utils.ts
+++ b/packages/compiler-core/src/utils.ts
@@ -6,12 +6,17 @@ import {
CallExpression,
SequenceExpression,
createSequenceExpression,
- createCallExpression
+ createCallExpression,
+ ExpressionNode,
+ CompoundExpressionNode,
+ createCompoundExpression,
+ DirectiveNode
} from './ast'
import { parse } from 'acorn'
import { walk } from 'estree-walker'
import { TransformContext } from './transform'
import { OPEN_BLOCK, CREATE_BLOCK } from './runtimeConstants'
+import { isString } from '@vue/shared'
// cache node requires
// lazy require dependencies so that they don't end up in rollup's dep graph
@@ -106,12 +111,28 @@ export function assert(condition: boolean, msg?: string) {
}
}
+export function findNonEmptyDir(
+ node: ElementNode,
+ name: string | RegExp
+): 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))
+ ) {
+ return p
+ }
+ }
+}
+
export function findProp(
- props: ElementNode['props'],
+ node: ElementNode,
name: string
): ElementNode['props'][0] | undefined {
- for (let i = 0; i < props.length; i++) {
- const p = props[i]
+ for (let i = 0; i < node.props.length; i++) {
+ const p = node.props[i]
if (p.type === NodeTypes.ATTRIBUTE) {
if (p.name === name && p.value && !p.value.isEmpty) {
return p
@@ -137,3 +158,18 @@ export function createBlockExpression(
createCallExpression(context.helper(CREATE_BLOCK), args)
])
}
+
+export function mergeExpressions(
+ ...args: (string | ExpressionNode)[]
+): CompoundExpressionNode {
+ const children: CompoundExpressionNode['children'] = []
+ for (let i = 0; i < args.length; i++) {
+ const exp = args[i]
+ if (isString(exp) || exp.type === NodeTypes.SIMPLE_EXPRESSION) {
+ children.push(exp)
+ } else {
+ children.push(...exp.children)
+ }
+ }
+ return createCompoundExpression(children)
+}
diff --git a/packages/runtime-core/src/componentSlots.ts b/packages/runtime-core/src/componentSlots.ts
index fb972c1b..67858ee3 100644
--- a/packages/runtime-core/src/componentSlots.ts
+++ b/packages/runtime-core/src/componentSlots.ts
@@ -51,7 +51,7 @@ export function resolveSlots(
let value = (children as RawSlots)[key]
if (isFunction(value)) {
;(slots as any)[key] = normalizeSlot(key, value)
- } else {
+ } else if (value != null) {
if (__DEV__) {
warn(
`Non-function value encountered for slot "${key}". ` +