diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
index c7796e24..e7f44eec 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
@@ -15,20 +15,6 @@ return function render() {
}"
`;
-exports[`compiler: transform component slots explicit default slot 1`] = `
-"const { toString, resolveComponent, createVNode, createBlock, openBlock } = Vue
-
-return function render() {
- const _ctx = this
- const _component_Comp = resolveComponent(\\"Comp\\")
-
- return (openBlock(), createBlock(_component_Comp, null, {
- default: ({ foo }) => [toString(foo), toString(_ctx.bar)],
- _compiled: true
- }))
-}"
-`;
-
exports[`compiler: transform component slots implicit default slot 1`] = `
"const { createVNode, resolveComponent, createBlock, openBlock } = Vue
@@ -146,6 +132,27 @@ return function render() {
}"
`;
+exports[`compiler: transform component slots named slots w/ implicit default slot 1`] = `
+"const _Vue = Vue
+
+return function render() {
+ with (this) {
+ const { createVNode: _createVNode, resolveComponent: _resolveComponent, createBlock: _createBlock, openBlock: _openBlock } = _Vue
+
+ const _component_Comp = _resolveComponent(\\"Comp\\")
+
+ return (_openBlock(), _createBlock(_component_Comp, null, {
+ one: () => [\\"foo\\"],
+ default: () => [
+ \\"bar\\",
+ _createVNode(\\"span\\")
+ ],
+ _compiled: true
+ }))
+ }
+}"
+`;
+
exports[`compiler: transform component slots nested slots scoping 1`] = `
"const { toString, resolveComponent, createVNode, createBlock, openBlock } = Vue
@@ -169,3 +176,17 @@ return function render() {
}))
}"
`;
+
+exports[`compiler: transform component slots on-component default slot 1`] = `
+"const { toString, resolveComponent, createVNode, createBlock, openBlock } = Vue
+
+return function render() {
+ const _ctx = this
+ const _component_Comp = resolveComponent(\\"Comp\\")
+
+ return (openBlock(), createBlock(_component_Comp, null, {
+ default: ({ foo }) => [toString(foo), toString(_ctx.bar)],
+ _compiled: true
+ }))
+}"
+`;
diff --git a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts
index 56be19aa..b313f6e1 100644
--- a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts
@@ -95,7 +95,7 @@ describe('compiler: transform component slots', () => {
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
- test('explicit default slot', () => {
+ test('on-component default slot', () => {
const { root, slots } = parseWithSlots(
`{{ foo }}{{ bar }}`,
{ prefixIdentifiers: true }
@@ -189,6 +189,43 @@ describe('compiler: transform component slots', () => {
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
+ test('named slots w/ implicit default slot', () => {
+ const { root, slots } = parseWithSlots(
+ `
+ foobar
+ `
+ )
+ expect(slots).toMatchObject(
+ createSlotMatcher({
+ one: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ params: undefined,
+ returns: [
+ {
+ type: NodeTypes.TEXT,
+ content: `foo`
+ }
+ ]
+ },
+ default: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ params: undefined,
+ returns: [
+ {
+ type: NodeTypes.TEXT,
+ content: `bar`
+ },
+ {
+ type: NodeTypes.ELEMENT,
+ tag: `span`
+ }
+ ]
+ }
+ })
+ )
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
test('dynamically named slots', () => {
const { root, slots } = parseWithSlots(
`
@@ -608,13 +645,13 @@ describe('compiler: transform component slots', () => {
})
describe('errors', () => {
- test('error on extraneous children w/ named slots', () => {
+ test('error on extraneous children w/ named default slot', () => {
const onError = jest.fn()
const source = `foobar`
parseWithSlots(source, { onError })
const index = source.indexOf('bar')
expect(onError.mock.calls[0][0]).toMatchObject({
- code: ErrorCodes.X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN,
+ code: ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
loc: {
source: `bar`,
start: {
diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts
index 6fd71521..c62d12b9 100644
--- a/packages/compiler-core/src/errors.ts
+++ b/packages/compiler-core/src/errors.ts
@@ -76,7 +76,7 @@ export const enum ErrorCodes {
X_V_SLOT_NAMED_SLOT_ON_COMPONENT,
X_V_SLOT_MIXED_SLOT_USAGE,
X_V_SLOT_DUPLICATE_SLOT_NAMES,
- X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN,
+ X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
X_V_SLOT_MISPLACED,
X_V_MODEL_NO_EXPRESSION,
X_V_MODEL_MALFORMED_EXPRESSION,
@@ -168,9 +168,9 @@ export const errorMessages: { [code: number]: string } = {
`The default slot should also use syntax when there are other ` +
`named slots to avoid scope ambiguity.`,
[ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES]: `Duplicate slot names found. `,
- [ErrorCodes.X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN]:
- `Extraneous children found when component has explicit slots. ` +
- `These children will be ignored.`,
+ [ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN]:
+ `Extraneous children found when component already has explicitly named ` +
+ `default slot. These children will be ignored.`,
[ErrorCodes.X_V_SLOT_MISPLACED]: `v-slot can only be used on components or tags.`,
[ErrorCodes.X_V_MODEL_NO_EXPRESSION]: `v-model is missing expression.`,
[ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION]: `v-model value must be a valid JavaScript member expression.`,
diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts
index f2ce7497..75481150 100644
--- a/packages/compiler-core/src/transforms/vSlot.ts
+++ b/packages/compiler-core/src/transforms/vSlot.ts
@@ -117,9 +117,9 @@ export function buildSlots(
// 1. Check for default slot with slotProps on component itself.
//
- const explicitDefaultSlot = findDir(node, 'slot', true)
- if (explicitDefaultSlot) {
- const { arg, exp, loc } = explicitDefaultSlot
+ const onComponentDefaultSlot = findDir(node, 'slot', true)
+ if (onComponentDefaultSlot) {
+ const { arg, exp, loc } = onComponentDefaultSlot
if (arg) {
context.onError(
createCompilerError(ErrorCodes.X_V_SLOT_NAMED_SLOT_ON_COMPONENT, loc)
@@ -131,8 +131,10 @@ export function buildSlots(
// 2. Iterate through children and check for template slots
//
let hasTemplateSlots = false
- let extraneousChild: TemplateChildNode | undefined = undefined
+ let hasNamedDefaultSlot = false
+ const implicitDefaultChildren: TemplateChildNode[] = []
const seenSlotNames = new Set()
+
for (let i = 0; i < children.length; i++) {
const slotElement = children[i]
let slotDir
@@ -142,13 +144,13 @@ export function buildSlots(
!(slotDir = findDir(slotElement, 'slot', true))
) {
// not a , skip.
- if (slotElement.type !== NodeTypes.COMMENT && !extraneousChild) {
- extraneousChild = slotElement
+ if (slotElement.type !== NodeTypes.COMMENT) {
+ implicitDefaultChildren.push(slotElement)
}
continue
}
- if (explicitDefaultSlot) {
+ if (onComponentDefaultSlot) {
// already has on-component default slot - this is incorrect usage.
context.onError(
createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, slotDir.loc)
@@ -267,23 +269,33 @@ export function buildSlots(
continue
}
seenSlotNames.add(staticSlotName)
+ if (staticSlotName === 'default') {
+ hasNamedDefaultSlot = true
+ }
}
slotsProperties.push(createObjectProperty(slotName, slotFunction))
}
}
- if (hasTemplateSlots && extraneousChild) {
- context.onError(
- createCompilerError(
- ErrorCodes.X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN,
- extraneousChild.loc
- )
- )
- }
-
- if (!explicitDefaultSlot && !hasTemplateSlots) {
- // implicit default slot.
- slotsProperties.push(buildDefaultSlot(undefined, children, loc))
+ if (!onComponentDefaultSlot) {
+ if (!hasTemplateSlots) {
+ // implicit default slot (on component)
+ slotsProperties.push(buildDefaultSlot(undefined, children, loc))
+ } else if (implicitDefaultChildren.length) {
+ // implicit default slot (mixed with named slots)
+ if (hasNamedDefaultSlot) {
+ context.onError(
+ createCompilerError(
+ ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
+ implicitDefaultChildren[0].loc
+ )
+ )
+ } else {
+ slotsProperties.push(
+ buildDefaultSlot(undefined, implicitDefaultChildren, loc)
+ )
+ }
+ }
}
let slots: ObjectExpression | CallExpression = createObjectExpression(