Skip to content

Commit a022b63

Browse files
committed
fix(compiler-core/slots): should support on-component named slots
1 parent 20f4965 commit a022b63

File tree

4 files changed

+121
-56
lines changed

4 files changed

+121
-56
lines changed

packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap

+40-14
Original file line numberDiff line numberDiff line change
@@ -113,20 +113,6 @@ return function render(_ctx, _cache) {
113113
}"
114114
`;
115115

116-
exports[`compiler: transform component slots named slots 1`] = `
117-
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
118-
119-
return function render(_ctx, _cache) {
120-
const _component_Comp = _resolveComponent(\\"Comp\\")
121-
122-
return (_openBlock(), _createBlock(_component_Comp, null, {
123-
one: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
124-
two: _withCtx(({ bar }) => [_toDisplayString(_ctx.foo), _toDisplayString(bar)]),
125-
_: 1
126-
}))
127-
}"
128-
`;
129-
130116
exports[`compiler: transform component slots named slots w/ implicit default slot 1`] = `
131117
"const _Vue = Vue
132118
@@ -171,6 +157,32 @@ return function render(_ctx, _cache) {
171157
}"
172158
`;
173159

160+
exports[`compiler: transform component slots on component dynamically named slot 1`] = `
161+
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
162+
163+
return function render(_ctx, _cache) {
164+
const _component_Comp = _resolveComponent(\\"Comp\\")
165+
166+
return (_openBlock(), _createBlock(_component_Comp, null, {
167+
[_ctx.named]: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
168+
_: 1
169+
}))
170+
}"
171+
`;
172+
173+
exports[`compiler: transform component slots on component named slot 1`] = `
174+
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
175+
176+
return function render(_ctx, _cache) {
177+
const _component_Comp = _resolveComponent(\\"Comp\\")
178+
179+
return (_openBlock(), _createBlock(_component_Comp, null, {
180+
named: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
181+
_: 1
182+
}))
183+
}"
184+
`;
185+
174186
exports[`compiler: transform component slots on-component default slot 1`] = `
175187
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
176188
@@ -183,3 +195,17 @@ return function render(_ctx, _cache) {
183195
}))
184196
}"
185197
`;
198+
199+
exports[`compiler: transform component slots template named slots 1`] = `
200+
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
201+
202+
return function render(_ctx, _cache) {
203+
const _component_Comp = _resolveComponent(\\"Comp\\")
204+
205+
return (_openBlock(), _createBlock(_component_Comp, null, {
206+
one: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
207+
two: _withCtx(({ bar }) => [_toDisplayString(_ctx.foo), _toDisplayString(bar)]),
208+
_: 1
209+
}))
210+
}"
211+
`;

packages/compiler-core/__tests__/transforms/vSlot.spec.ts

+67-24
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,40 @@ describe('compiler: transform component slots', () => {
130130
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
131131
})
132132

133-
test('named slots', () => {
133+
test('on component named slot', () => {
134+
const { root, slots } = parseWithSlots(
135+
`<Comp v-slot:named="{ foo }">{{ foo }}{{ bar }}</Comp>`,
136+
{ prefixIdentifiers: true }
137+
)
138+
expect(slots).toMatchObject(
139+
createSlotMatcher({
140+
named: {
141+
type: NodeTypes.JS_FUNCTION_EXPRESSION,
142+
params: {
143+
type: NodeTypes.COMPOUND_EXPRESSION,
144+
children: [`{ `, { content: `foo` }, ` }`]
145+
},
146+
returns: [
147+
{
148+
type: NodeTypes.INTERPOLATION,
149+
content: {
150+
content: `foo`
151+
}
152+
},
153+
{
154+
type: NodeTypes.INTERPOLATION,
155+
content: {
156+
content: `_ctx.bar`
157+
}
158+
}
159+
]
160+
}
161+
})
162+
)
163+
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
164+
})
165+
166+
test('template named slots', () => {
134167
const { root, slots } = parseWithSlots(
135168
`<Comp>
136169
<template v-slot:one="{ foo }">
@@ -191,6 +224,39 @@ describe('compiler: transform component slots', () => {
191224
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
192225
})
193226

227+
test('on component dynamically named slot', () => {
228+
const { root, slots } = parseWithSlots(
229+
`<Comp v-slot:[named]="{ foo }">{{ foo }}{{ bar }}</Comp>`,
230+
{ prefixIdentifiers: true }
231+
)
232+
expect(slots).toMatchObject(
233+
createSlotMatcher({
234+
'[_ctx.named]': {
235+
type: NodeTypes.JS_FUNCTION_EXPRESSION,
236+
params: {
237+
type: NodeTypes.COMPOUND_EXPRESSION,
238+
children: [`{ `, { content: `foo` }, ` }`]
239+
},
240+
returns: [
241+
{
242+
type: NodeTypes.INTERPOLATION,
243+
content: {
244+
content: `foo`
245+
}
246+
},
247+
{
248+
type: NodeTypes.INTERPOLATION,
249+
content: {
250+
content: `_ctx.bar`
251+
}
252+
}
253+
]
254+
}
255+
})
256+
)
257+
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
258+
})
259+
194260
test('named slots w/ implicit default slot', () => {
195261
const { root, slots } = parseWithSlots(
196262
`<Comp>
@@ -736,28 +802,5 @@ describe('compiler: transform component slots', () => {
736802
}
737803
})
738804
})
739-
740-
test('error on named slot on component', () => {
741-
const onError = jest.fn()
742-
const source = `<Comp v-slot:foo>foo</Comp>`
743-
parseWithSlots(source, { onError })
744-
const index = source.indexOf('v-slot')
745-
expect(onError.mock.calls[0][0]).toMatchObject({
746-
code: ErrorCodes.X_V_SLOT_NAMED_SLOT_ON_COMPONENT,
747-
loc: {
748-
source: `v-slot:foo`,
749-
start: {
750-
offset: index,
751-
line: 1,
752-
column: index + 1
753-
},
754-
end: {
755-
offset: index + 10,
756-
line: 1,
757-
column: index + 11
758-
}
759-
}
760-
})
761-
})
762805
})
763806
})

packages/compiler-core/src/errors.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ export const enum ErrorCodes {
7676
X_V_BIND_NO_EXPRESSION,
7777
X_V_ON_NO_EXPRESSION,
7878
X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
79-
X_V_SLOT_NAMED_SLOT_ON_COMPONENT,
8079
X_V_SLOT_MIXED_SLOT_USAGE,
8180
X_V_SLOT_DUPLICATE_SLOT_NAMES,
8281
X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
@@ -163,13 +162,10 @@ export const errorMessages: { [code: number]: string } = {
163162
[ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
164163
[ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`,
165164
[ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `Unexpected custom directive on <slot> outlet.`,
166-
[ErrorCodes.X_V_SLOT_NAMED_SLOT_ON_COMPONENT]:
167-
`Named v-slot on component. ` +
168-
`Named slots should use <template v-slot> syntax nested inside the component.`,
169165
[ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE]:
170166
`Mixed v-slot usage on both the component and nested <template>.` +
171-
`The default slot should also use <template> syntax when there are other ` +
172-
`named slots to avoid scope ambiguity.`,
167+
`When there are multiple named slots, all slots should use <template> ` +
168+
`syntax to avoid scope ambiguity.`,
173169
[ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES]: `Duplicate slot names found. `,
174170
[ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN]:
175171
`Extraneous children found when component already has explicitly named ` +

packages/compiler-core/src/transforms/vSlot.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -139,17 +139,17 @@ export function buildSlots(
139139
hasDynamicSlots = hasScopeRef(node, context.identifiers)
140140
}
141141

142-
// 1. Check for default slot with slotProps on component itself.
142+
// 1. Check for slot with slotProps on component itself.
143143
// <Comp v-slot="{ prop }"/>
144-
const onComponentDefaultSlot = findDir(node, 'slot', true)
145-
if (onComponentDefaultSlot) {
146-
const { arg, exp, loc } = onComponentDefaultSlot
147-
if (arg) {
148-
context.onError(
149-
createCompilerError(ErrorCodes.X_V_SLOT_NAMED_SLOT_ON_COMPONENT, loc)
144+
const onComponentSlot = findDir(node, 'slot', true)
145+
if (onComponentSlot) {
146+
const { arg, exp } = onComponentSlot
147+
slotsProperties.push(
148+
createObjectProperty(
149+
arg || createSimpleExpression('default', true),
150+
buildSlotFn(exp, children, loc)
150151
)
151-
}
152-
slotsProperties.push(buildDefaultSlotProperty(exp, children))
152+
)
153153
}
154154

155155
// 2. Iterate through children and check for template slots
@@ -174,8 +174,8 @@ export function buildSlots(
174174
continue
175175
}
176176

177-
if (onComponentDefaultSlot) {
178-
// already has on-component default slot - this is incorrect usage.
177+
if (onComponentSlot) {
178+
// already has on-component slot - this is incorrect usage.
179179
context.onError(
180180
createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, slotDir.loc)
181181
)
@@ -294,7 +294,7 @@ export function buildSlots(
294294
}
295295
}
296296

297-
if (!onComponentDefaultSlot) {
297+
if (!onComponentSlot) {
298298
if (!hasTemplateSlots) {
299299
// implicit default slot (on component)
300300
slotsProperties.push(buildDefaultSlotProperty(undefined, children))

0 commit comments

Comments
 (0)