diff --git a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts
index 4d11e9e0..d7a36196 100644
--- a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts
@@ -3,7 +3,8 @@ import {
parse,
transform,
ElementNode,
- NodeTypes
+ NodeTypes,
+ generate
} from '../../src'
import { transformElement } from '../../src/transforms/transformElement'
import { transformOn } from '../../src/transforms/vOn'
@@ -320,4 +321,10 @@ describe('compiler: transform slots', () => {
]
})
})
+
+ test('generate slot', () => {
+ const ast = parseWithSlots(``)
+ const { code } = generate(ast)
+ console.log(code)
+ })
})
diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts
index 4bfb4bea..d86b325a 100644
--- a/packages/compiler-core/src/ast.ts
+++ b/packages/compiler-core/src/ast.ts
@@ -27,7 +27,8 @@ export const enum NodeTypes {
JS_CALL_EXPRESSION,
JS_OBJECT_EXPRESSION,
JS_PROPERTY,
- JS_ARRAY_EXPRESSION
+ JS_ARRAY_EXPRESSION,
+ JS_SLOT_FUNCTION
}
export const enum ElementTypes {
@@ -157,6 +158,7 @@ export type JSChildNode =
| ObjectExpression
| ArrayExpression
| ExpressionNode
+ | SlotFunctionExpression
export interface CallExpression extends Node {
type: NodeTypes.JS_CALL_EXPRESSION
@@ -180,6 +182,12 @@ export interface ArrayExpression extends Node {
elements: Array
}
+export interface SlotFunctionExpression extends Node {
+ type: NodeTypes.JS_SLOT_FUNCTION
+ params: ExpressionNode | undefined
+ returns: ChildNode[]
+}
+
export function createArrayExpression(
elements: ArrayExpression['elements'],
loc: SourceLocation
@@ -264,3 +272,16 @@ export function createCallExpression(
arguments: args
}
}
+
+export function createFunctionExpression(
+ params: ExpressionNode | undefined,
+ returns: ChildNode[],
+ loc: SourceLocation
+): SlotFunctionExpression {
+ return {
+ type: NodeTypes.JS_SLOT_FUNCTION,
+ params,
+ returns,
+ loc
+ }
+}
diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts
index 9405587f..a2700bbf 100644
--- a/packages/compiler-core/src/codegen.ts
+++ b/packages/compiler-core/src/codegen.ts
@@ -18,7 +18,8 @@ import {
InterpolationNode,
CompoundExpressionNode,
SimpleExpressionNode,
- ElementTypes
+ ElementTypes,
+ SlotFunctionExpression
} from './ast'
import { SourceMapGenerator, RawSourceMap } from 'source-map'
import {
@@ -364,10 +365,17 @@ function genNode(node: CodegenNode, context: CodegenContext) {
case NodeTypes.JS_ARRAY_EXPRESSION:
genArrayExpression(node, context)
break
+ case NodeTypes.JS_SLOT_FUNCTION:
+ genSlotFunction(node, context)
+ break
default:
- /* istanbul ignore next */
- __DEV__ &&
+ /* istanbul ignore if */
+ if (__DEV__) {
assert(false, `unhandled codegen node type: ${(node as any).type}`)
+ // make sure we exhaust all possible types
+ const exhaustiveCheck: never = node
+ return exhaustiveCheck
+ }
}
}
@@ -568,3 +576,14 @@ function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
function genArrayExpression(node: ArrayExpression, context: CodegenContext) {
genNodeListAsArray(node.elements, context)
}
+
+function genSlotFunction(
+ node: SlotFunctionExpression,
+ context: CodegenContext
+) {
+ context.push(`(`, node)
+ if (node.params) genNode(node.params, context)
+ context.push(`) => `)
+ // pre-normalized slots should always return arrays
+ genNodeListAsArray(node.returns, context)
+}
diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts
index a3de21fc..8efb901b 100644
--- a/packages/compiler-core/src/errors.ts
+++ b/packages/compiler-core/src/errors.ts
@@ -69,6 +69,10 @@ export const enum ErrorCodes {
X_V_BIND_NO_EXPRESSION,
X_V_ON_NO_EXPRESSION,
X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
+ X_NAMED_SLOT_ON_COMPONENT,
+ X_MIXED_SLOT_USAGE,
+ X_DUPLICATE_SLOT_NAMES,
+ X_EXTRANEOUS_NON_SLOT_CHILDREN,
// generic errors
X_PREFIX_ID_NOT_SUPPORTED,
@@ -133,14 +137,24 @@ export const errorMessages: { [code: number]: string } = {
'Note that dynamic directive argument connot contain spaces.',
// transform errors
- [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_FOR_NO_EXPRESSION]: `v-for has no expression`,
- [ErrorCodes.X_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression`,
- [ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression`,
- [ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression`,
- [ErrorCodes.X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `unexpected custom directive on outlet`,
-
+ [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_FOR_NO_EXPRESSION]: `v-for has no expression.`,
+ [ErrorCodes.X_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`,
+ [ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
+ [ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`,
+ [ErrorCodes.X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `Unexpected custom directive on outlet.`,
+ [ErrorCodes.X_NAMED_SLOT_ON_COMPONENT]:
+ `Named v-slot on component. ` +
+ `Named slots should use syntax nested inside the component.`,
+ [ErrorCodes.X_MIXED_SLOT_USAGE]:
+ `Mixed v-slot usage on both the component and nested .` +
+ `The default slot should also use syntax when there are other ` +
+ `named slots to avoid scope ambiguity.`,
+ [ErrorCodes.X_DUPLICATE_SLOT_NAMES]: `Duplicate slot names found. `,
+ [ErrorCodes.X_EXTRANEOUS_NON_SLOT_CHILDREN]:
+ `Extraneous children found when component has explicit slots. ` +
+ `These children will be ignored.`,
// generic errors
[ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
[ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED]: `ES module mode is not supported in this build of compiler.`
diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts
index 10378540..afbd64b2 100644
--- a/packages/compiler-core/src/transforms/transformElement.ts
+++ b/packages/compiler-core/src/transforms/transformElement.ts
@@ -141,6 +141,12 @@ export function buildProps(
// directives
isStatic = false
const { name, arg, exp, loc } = prop
+
+ // skip v-slot - it is handled by its dedicated transform.
+ if (name === 'slot') {
+ continue
+ }
+
// special case for v-bind and v-on with no argument
const isBind = name === 'bind'
if (!arg && (isBind || name === 'on')) {
diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts
index 74cac5ca..67037179 100644
--- a/packages/compiler-core/src/transforms/vSlot.ts
+++ b/packages/compiler-core/src/transforms/vSlot.ts
@@ -6,22 +6,127 @@ import {
createCompoundExpression,
createCallExpression,
CompoundExpressionNode,
- CallExpression
+ CallExpression,
+ createObjectProperty,
+ createSimpleExpression,
+ createFunctionExpression,
+ DirectiveNode,
+ ElementTypes,
+ ExpressionNode,
+ Property,
+ ChildNode,
+ SourceLocation
} from '../ast'
import { TransformContext } from '../transform'
import { buildProps } from './transformElement'
import { createCompilerError, ErrorCodes } from '../errors'
import { isSimpleIdentifier } from '../utils'
import { RENDER_SLOT } from '../runtimeConstants'
+import { isString } from '@vue/shared'
+
+const isVSlot = (p: ElementNode['props'][0]): p is DirectiveNode =>
+ p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
export function buildSlots(
- { loc, children }: ElementNode,
+ { props, children, loc }: ElementNode,
context: TransformContext
): ObjectExpression {
- const slots = createObjectExpression([], loc)
- // TODO
+ const slots: Property[] = []
- return slots
+ // 1. Check for default slot with slotProps on component itself.
+ //
+ const explicitDefaultSlot = props.find(isVSlot)
+ if (explicitDefaultSlot) {
+ const { arg, exp, loc } = explicitDefaultSlot
+ if (arg) {
+ context.onError(
+ createCompilerError(ErrorCodes.X_NAMED_SLOT_ON_COMPONENT, loc)
+ )
+ }
+ slots.push(buildSlot(`default`, exp, children, loc))
+ }
+
+ // 2. Iterate through children and check for template slots
+ //
+ let hasTemplateSlots = false
+ const seenSlotNames = new Set()
+ const nonSlotChildren: ChildNode[] = []
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i]
+ if (
+ child.type === NodeTypes.ELEMENT &&
+ child.tagType === ElementTypes.TEMPLATE
+ ) {
+ const { props, children, loc: nodeLoc } = child
+ const slotDir = props.find(isVSlot)
+ if (slotDir) {
+ hasTemplateSlots = true
+ 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)
+ )
+ break
+ } else {
+ // check duplicate slot names
+ if (
+ !slotName ||
+ (slotName.type === NodeTypes.SIMPLE_EXPRESSION && slotName.isStatic)
+ ) {
+ const name = slotName ? slotName.content : `default`
+ if (seenSlotNames.has(name)) {
+ context.onError(
+ createCompilerError(ErrorCodes.X_DUPLICATE_SLOT_NAMES, dirLoc)
+ )
+ continue
+ }
+ seenSlotNames.add(name)
+ }
+ slots.push(
+ buildSlot(slotName || `default`, slotProps, children, nodeLoc)
+ )
+ }
+ } else {
+ nonSlotChildren.push(child)
+ }
+ } else {
+ nonSlotChildren.push(child)
+ }
+ }
+
+ if (hasTemplateSlots && nonSlotChildren.length) {
+ context.onError(
+ createCompilerError(
+ ErrorCodes.X_EXTRANEOUS_NON_SLOT_CHILDREN,
+ nonSlotChildren[0].loc
+ )
+ )
+ }
+
+ if (!explicitDefaultSlot && !hasTemplateSlots) {
+ // implicit default slot.
+ slots.push(buildSlot(`default`, undefined, children, loc))
+ }
+
+ return createObjectExpression(slots, loc)
+}
+
+function buildSlot(
+ name: string | ExpressionNode,
+ slotProps: ExpressionNode | undefined,
+ children: ChildNode[],
+ loc: SourceLocation
+): Property {
+ return createObjectProperty(
+ isString(name) ? createSimpleExpression(name, true, loc) : name,
+ createFunctionExpression(
+ slotProps,
+ children,
+ children.length ? children[0].loc : loc
+ ),
+ loc
+ )
}
export function buildSlotOutlet(node: ElementNode, context: TransformContext) {
@@ -84,7 +189,10 @@ export function buildSlotOutlet(node: ElementNode, context: TransformContext) {
)
if (directives.length) {
context.onError(
- createCompilerError(ErrorCodes.X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET)
+ createCompilerError(
+ ErrorCodes.X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
+ directives[0].loc
+ )
)
}
slotArgs.push(propsExpression)