Skip to content

fix(compiler-vapor): don't generate default slot for whitespace when preserved #13009

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,68 @@ export function render(_ctx) {
return n6
}"
`;

exports[`compiler: transform slot > with whitespace: 'preserve' > implicit default slot 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
const t0 = _template(" Header ")
const t1 = _template(" ")
const t2 = _template("<p></p>")

export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n4 = _createComponentWithFallback(_component_Comp, null, {
"header": () => {
const n0 = t0()
return n0
},
"default": () => {
const n2 = t1()
const n3 = t2()
return [n2, n3]
}
}, true)
return n4
}"
`;

exports[`compiler: transform slot > with whitespace: 'preserve' > named default slot + implicit whitespace content 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
const t0 = _template(" Header ")
const t1 = _template(" Default ")

export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n5 = _createComponentWithFallback(_component_Comp, null, {
"header": () => {
const n0 = t0()
return n0
},
"default": () => {
const n3 = t1()
return n3
}
}, true)
return n5
}"
`;

exports[`compiler: transform slot > with whitespace: 'preserve' > should not generate whitespace only default slot 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
const t0 = _template(" Header ")
const t1 = _template(" Footer ")

export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n5 = _createComponentWithFallback(_component_Comp, null, {
"header": () => {
const n0 = t0()
return n0
},
"footer": () => {
const n3 = t1()
return n3
}
}, true)
return n5
}"
`;
56 changes: 56 additions & 0 deletions packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,4 +509,60 @@ describe('compiler: transform slot', () => {
})
})
})

describe(`with whitespace: 'preserve'`, () => {
test('named default slot + implicit whitespace content', () => {
const source = `
<Comp>
<template #header> Header </template>
<template #default> Default </template>
</Comp>
`
const { code } = compileWithSlots(source, {
whitespace: 'preserve',
})

expect(
`Extraneous children found when component already has explicitly named default slot.`,
).not.toHaveBeenWarned()
expect(code).toMatchSnapshot()
})

test('implicit default slot', () => {
const source = `
<Comp>
<template #header> Header </template>
<p/>
</Comp>
`
const { code } = compileWithSlots(source, {
whitespace: 'preserve',
})

expect(
`Extraneous children found when component already has explicitly named default slot.`,
).not.toHaveBeenWarned()
expect(code).toMatchSnapshot()
})

test('should not generate whitespace only default slot', () => {
const source = `
<Comp>
<template #header> Header </template>
<template #footer> Footer </template>
</Comp>
`
const { code, ir } = compileWithSlots(source, {
whitespace: 'preserve',
})

const slots = (ir.block.dynamic.children[0].operation as any).slots[0]
.slots
// should be: header, footer (no default)
expect(Object.keys(slots).length).toBe(2)
expect(!!slots['default']).toBe(false)

expect(code).toMatchSnapshot()
})
})
})
11 changes: 9 additions & 2 deletions packages/compiler-vapor/src/transforms/transformText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ const seen = new WeakMap<
WeakSet<TemplateChildNode | RootNode>
>()

export function markNonTemplate(
node: TemplateChildNode,
context: TransformContext,
): void {
seen.get(context.root)!.add(node)
}

export const transformText: NodeTransform = (node, context) => {
if (!seen.has(context.root)) seen.set(context.root, new WeakSet())
if (seen.get(context.root)!.has(node)) {
Expand Down Expand Up @@ -68,7 +75,7 @@ export const transformText: NodeTransform = (node, context) => {
prev.type === NodeTypes.TEXT
) {
// mark leading text node for skipping
seen.get(context.root)!.add(prev)
markNonTemplate(prev, context)
}
}
}
Expand Down Expand Up @@ -143,7 +150,7 @@ function processTextContainer(
}

function createTextLikeExpression(node: TextLike, context: TransformContext) {
seen.get(context.root)!.add(node)
markNonTemplate(node, context)
if (node.type === NodeTypes.TEXT) {
return createSimpleExpression(node.content, true, node.loc)
} else {
Expand Down
21 changes: 16 additions & 5 deletions packages/compiler-vapor/src/transforms/vSlot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
type VaporDirectiveNode,
} from '../ir'
import { findDir, resolveExpression } from '../utils'
import { markNonTemplate } from './transformText'

export const transformVSlot: NodeTransform = (node, context) => {
if (node.type !== NodeTypes.ELEMENT) return
Expand Down Expand Up @@ -66,11 +67,21 @@ function transformComponentSlot(
) {
const { children } = node
const arg = dir && dir.arg
const nonSlotTemplateChildren = children.filter(
n =>
isNonWhitespaceContent(node) &&
!(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot)),
)

// whitespace: 'preserve'
const emptyTextNodes: TemplateChildNode[] = []
const nonSlotTemplateChildren = children.filter(n => {
if (isNonWhitespaceContent(n)) {
return !(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot))
} else {
emptyTextNodes.push(n)
}
})
if (!nonSlotTemplateChildren.length) {
emptyTextNodes.forEach(n => {
markNonTemplate(n, context)
})
}

const [block, onExit] = createSlotBlock(node, dir, context)

Expand Down