fix(compiler/v-slot): handle implicit default slot mixed with named slots
This commit is contained in:
parent
bb6a346996
commit
2ac4b723e0
@ -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
|
||||
}))
|
||||
}"
|
||||
`;
|
||||
|
@ -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(
|
||||
`<Comp v-slot="{ foo }">{{ foo }}{{ bar }}</Comp>`,
|
||||
{ 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(
|
||||
`<Comp>
|
||||
<template #one>foo</template>bar<span/>
|
||||
</Comp>`
|
||||
)
|
||||
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(
|
||||
`<Comp>
|
||||
@ -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 = `<Comp><template #default>foo</template>bar</Comp>`
|
||||
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: {
|
||||
|
@ -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 <template> 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 <template> 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.`,
|
||||
|
@ -117,9 +117,9 @@ export function buildSlots(
|
||||
|
||||
// 1. Check for default slot with slotProps on component itself.
|
||||
// <Comp v-slot="{ prop }"/>
|
||||
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
|
||||
// <template v-slot:foo="{ prop }">
|
||||
let hasTemplateSlots = false
|
||||
let extraneousChild: TemplateChildNode | undefined = undefined
|
||||
let hasNamedDefaultSlot = false
|
||||
const implicitDefaultChildren: TemplateChildNode[] = []
|
||||
const seenSlotNames = new Set<string>()
|
||||
|
||||
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 <template v-slot>, 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) {
|
||||
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_NON_SLOT_CHILDREN,
|
||||
extraneousChild.loc
|
||||
ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
|
||||
implicitDefaultChildren[0].loc
|
||||
)
|
||||
)
|
||||
} else {
|
||||
slotsProperties.push(
|
||||
buildDefaultSlot(undefined, implicitDefaultChildren, loc)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!explicitDefaultSlot && !hasTemplateSlots) {
|
||||
// implicit default slot.
|
||||
slotsProperties.push(buildDefaultSlot(undefined, children, loc))
|
||||
}
|
||||
|
||||
let slots: ObjectExpression | CallExpression = createObjectExpression(
|
||||
|
Loading…
x
Reference in New Issue
Block a user