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 5bb9ea6e..008fc7ba 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
@@ -209,3 +209,48 @@ return function render(_ctx, _cache) {
}))
}"
`;
+
+exports[`compiler: transform component slots with whitespace: 'preserve' implicit default slot 1`] = `
+"const { createVNode: _createVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+ const _component_Comp = _resolveComponent(\\"Comp\\")
+
+ return (_openBlock(), _createBlock(_component_Comp, null, {
+ header: _withCtx(() => [\\" Header \\"]),
+ default: _withCtx(() => [
+ \\" \\",
+ _createVNode(\\"p\\")
+ ]),
+ _: 1 /* STABLE */
+ }))
+}"
+`;
+
+exports[`compiler: transform component slots with whitespace: 'preserve' named default slot + implicit whitespace content 1`] = `
+"const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+ const _component_Comp = _resolveComponent(\\"Comp\\")
+
+ return (_openBlock(), _createBlock(_component_Comp, null, {
+ header: _withCtx(() => [\\" Header \\"]),
+ default: _withCtx(() => [\\" Default \\"]),
+ _: 1 /* STABLE */
+ }))
+}"
+`;
+
+exports[`compiler: transform component slots with whitespace: 'preserve' should not generate whitespace only default slot 1`] = `
+"const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+ const _component_Comp = _resolveComponent(\\"Comp\\")
+
+ return (_openBlock(), _createBlock(_component_Comp, null, {
+ header: _withCtx(() => [\\" Header \\"]),
+ footer: _withCtx(() => [\\" Footer \\"]),
+ _: 1 /* STABLE */
+ }))
+}"
+`;
diff --git a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts
index 99fc009b..beddd212 100644
--- a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts
@@ -9,7 +9,9 @@ import {
ForNode,
ComponentNode,
VNodeCall,
- SlotsExpression
+ SlotsExpression,
+ ObjectExpression,
+ SimpleExpressionNode
} from '../../src'
import { transformElement } from '../../src/transforms/transformElement'
import { transformOn } from '../../src/transforms/vOn'
@@ -27,7 +29,9 @@ import { transformFor } from '../../src/transforms/vFor'
import { transformIf } from '../../src/transforms/vIf'
function parseWithSlots(template: string, options: CompilerOptions = {}) {
- const ast = parse(template)
+ const ast = parse(template, {
+ whitespace: options.whitespace
+ })
transform(ast, {
nodeTransforms: [
transformIf,
@@ -862,4 +866,64 @@ describe('compiler: transform component slots', () => {
})
})
})
+
+ describe(`with whitespace: 'preserve'`, () => {
+ test('named default slot + implicit whitespace content', () => {
+ const source = `
+
+ Header
+ Default
+
+ `
+ const { root } = parseWithSlots(source, {
+ whitespace: 'preserve'
+ })
+
+ expect(
+ `Extraneous children found when component already has explicitly named default slot.`
+ ).not.toHaveBeenWarned()
+ expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
+ })
+
+ test('implicit default slot', () => {
+ const source = `
+
+ Header
+
+
+ `
+ const { root } = parseWithSlots(source, {
+ whitespace: 'preserve'
+ })
+
+ expect(
+ `Extraneous children found when component already has explicitly named default slot.`
+ ).not.toHaveBeenWarned()
+ expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
+ })
+
+ test('should not generate whitespace only default slot', () => {
+ const source = `
+
+ Header
+ Footer
+
+ `
+ const { root } = parseWithSlots(source, {
+ whitespace: 'preserve'
+ })
+
+ // slots is vnodeCall's children as an ObjectExpression
+ const slots = (root as any).children[0].codegenNode.children
+ .properties as ObjectExpression['properties']
+
+ // should be: header, footer, _ (no default)
+ expect(slots.length).toBe(3)
+ expect(
+ slots.some(p => (p.key as SimpleExpressionNode).content === 'default')
+ ).toBe(false)
+
+ expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
+ })
+ })
})
diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts
index 4c249d23..327bac44 100644
--- a/packages/compiler-core/src/transforms/vSlot.ts
+++ b/packages/compiler-core/src/transforms/vSlot.ts
@@ -311,7 +311,13 @@ export function buildSlots(
if (!hasTemplateSlots) {
// implicit default slot (on component)
slotsProperties.push(buildDefaultSlotProperty(undefined, children))
- } else if (implicitDefaultChildren.length) {
+ } else if (
+ implicitDefaultChildren.length &&
+ // #3766
+ // with whitespace: 'preserve', whitespaces between slots will end up in
+ // implicitDefaultChildren. Ignore if all implicit children are whitespaces.
+ implicitDefaultChildren.some(node => isNonWhitespaceContent(node))
+ ) {
// implicit default slot (mixed with named slots)
if (hasNamedDefaultSlot) {
context.onError(
@@ -397,3 +403,11 @@ function hasForwardedSlots(children: TemplateChildNode[]): boolean {
}
return false
}
+
+function isNonWhitespaceContent(node: TemplateChildNode): boolean {
+ if (node.type !== NodeTypes.TEXT && node.type !== NodeTypes.TEXT_CALL)
+ return true
+ return node.type === NodeTypes.TEXT
+ ? !!node.content.trim()
+ : isNonWhitespaceContent(node.content)
+}