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`] = `
|
exports[`compiler: transform component slots implicit default slot 1`] = `
|
||||||
"const { createVNode, resolveComponent, createBlock, openBlock } = Vue
|
"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`] = `
|
exports[`compiler: transform component slots nested slots scoping 1`] = `
|
||||||
"const { toString, resolveComponent, createVNode, createBlock, openBlock } = Vue
|
"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()
|
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('explicit default slot', () => {
|
test('on-component default slot', () => {
|
||||||
const { root, slots } = parseWithSlots(
|
const { root, slots } = parseWithSlots(
|
||||||
`<Comp v-slot="{ foo }">{{ foo }}{{ bar }}</Comp>`,
|
`<Comp v-slot="{ foo }">{{ foo }}{{ bar }}</Comp>`,
|
||||||
{ prefixIdentifiers: true }
|
{ prefixIdentifiers: true }
|
||||||
@ -189,6 +189,43 @@ describe('compiler: transform component slots', () => {
|
|||||||
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
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', () => {
|
test('dynamically named slots', () => {
|
||||||
const { root, slots } = parseWithSlots(
|
const { root, slots } = parseWithSlots(
|
||||||
`<Comp>
|
`<Comp>
|
||||||
@ -608,13 +645,13 @@ describe('compiler: transform component slots', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('errors', () => {
|
describe('errors', () => {
|
||||||
test('error on extraneous children w/ named slots', () => {
|
test('error on extraneous children w/ named default slot', () => {
|
||||||
const onError = jest.fn()
|
const onError = jest.fn()
|
||||||
const source = `<Comp><template #default>foo</template>bar</Comp>`
|
const source = `<Comp><template #default>foo</template>bar</Comp>`
|
||||||
parseWithSlots(source, { onError })
|
parseWithSlots(source, { onError })
|
||||||
const index = source.indexOf('bar')
|
const index = source.indexOf('bar')
|
||||||
expect(onError.mock.calls[0][0]).toMatchObject({
|
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: {
|
loc: {
|
||||||
source: `bar`,
|
source: `bar`,
|
||||||
start: {
|
start: {
|
||||||
|
@ -76,7 +76,7 @@ export const enum ErrorCodes {
|
|||||||
X_V_SLOT_NAMED_SLOT_ON_COMPONENT,
|
X_V_SLOT_NAMED_SLOT_ON_COMPONENT,
|
||||||
X_V_SLOT_MIXED_SLOT_USAGE,
|
X_V_SLOT_MIXED_SLOT_USAGE,
|
||||||
X_V_SLOT_DUPLICATE_SLOT_NAMES,
|
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_SLOT_MISPLACED,
|
||||||
X_V_MODEL_NO_EXPRESSION,
|
X_V_MODEL_NO_EXPRESSION,
|
||||||
X_V_MODEL_MALFORMED_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 ` +
|
`The default slot should also use <template> syntax when there are other ` +
|
||||||
`named slots to avoid scope ambiguity.`,
|
`named slots to avoid scope ambiguity.`,
|
||||||
[ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES]: `Duplicate slot names found. `,
|
[ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES]: `Duplicate slot names found. `,
|
||||||
[ErrorCodes.X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN]:
|
[ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN]:
|
||||||
`Extraneous children found when component has explicit slots. ` +
|
`Extraneous children found when component already has explicitly named ` +
|
||||||
`These children will be ignored.`,
|
`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_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_NO_EXPRESSION]: `v-model is missing expression.`,
|
||||||
[ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION]: `v-model value must be a valid JavaScript member 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.
|
// 1. Check for default slot with slotProps on component itself.
|
||||||
// <Comp v-slot="{ prop }"/>
|
// <Comp v-slot="{ prop }"/>
|
||||||
const explicitDefaultSlot = findDir(node, 'slot', true)
|
const onComponentDefaultSlot = findDir(node, 'slot', true)
|
||||||
if (explicitDefaultSlot) {
|
if (onComponentDefaultSlot) {
|
||||||
const { arg, exp, loc } = explicitDefaultSlot
|
const { arg, exp, loc } = onComponentDefaultSlot
|
||||||
if (arg) {
|
if (arg) {
|
||||||
context.onError(
|
context.onError(
|
||||||
createCompilerError(ErrorCodes.X_V_SLOT_NAMED_SLOT_ON_COMPONENT, loc)
|
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
|
// 2. Iterate through children and check for template slots
|
||||||
// <template v-slot:foo="{ prop }">
|
// <template v-slot:foo="{ prop }">
|
||||||
let hasTemplateSlots = false
|
let hasTemplateSlots = false
|
||||||
let extraneousChild: TemplateChildNode | undefined = undefined
|
let hasNamedDefaultSlot = false
|
||||||
|
const implicitDefaultChildren: TemplateChildNode[] = []
|
||||||
const seenSlotNames = new Set<string>()
|
const seenSlotNames = new Set<string>()
|
||||||
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
const slotElement = children[i]
|
const slotElement = children[i]
|
||||||
let slotDir
|
let slotDir
|
||||||
@ -142,13 +144,13 @@ export function buildSlots(
|
|||||||
!(slotDir = findDir(slotElement, 'slot', true))
|
!(slotDir = findDir(slotElement, 'slot', true))
|
||||||
) {
|
) {
|
||||||
// not a <template v-slot>, skip.
|
// not a <template v-slot>, skip.
|
||||||
if (slotElement.type !== NodeTypes.COMMENT && !extraneousChild) {
|
if (slotElement.type !== NodeTypes.COMMENT) {
|
||||||
extraneousChild = slotElement
|
implicitDefaultChildren.push(slotElement)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (explicitDefaultSlot) {
|
if (onComponentDefaultSlot) {
|
||||||
// already has on-component default slot - this is incorrect usage.
|
// already has on-component default slot - this is incorrect usage.
|
||||||
context.onError(
|
context.onError(
|
||||||
createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, slotDir.loc)
|
createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, slotDir.loc)
|
||||||
@ -267,23 +269,33 @@ export function buildSlots(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seenSlotNames.add(staticSlotName)
|
seenSlotNames.add(staticSlotName)
|
||||||
|
if (staticSlotName === 'default') {
|
||||||
|
hasNamedDefaultSlot = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
slotsProperties.push(createObjectProperty(slotName, slotFunction))
|
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(
|
context.onError(
|
||||||
createCompilerError(
|
createCompilerError(
|
||||||
ErrorCodes.X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN,
|
ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
|
||||||
extraneousChild.loc
|
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(
|
let slots: ObjectExpression | CallExpression = createObjectExpression(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user