Skip to content

Commit 47da921

Browse files
authored
fix(compiler-core): fix whitespace management for slots with whitespace: 'preserve' (#3767)
fix #3766
1 parent f3d3036 commit 47da921

File tree

3 files changed

+126
-3
lines changed

3 files changed

+126
-3
lines changed

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

+45
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,48 @@ return function render(_ctx, _cache) {
209209
}))
210210
}"
211211
`;
212+
213+
exports[`compiler: transform component slots with whitespace: 'preserve' implicit default slot 1`] = `
214+
"const { createVNode: _createVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
215+
216+
return function render(_ctx, _cache) {
217+
const _component_Comp = _resolveComponent(\\"Comp\\")
218+
219+
return (_openBlock(), _createBlock(_component_Comp, null, {
220+
header: _withCtx(() => [\\" Header \\"]),
221+
default: _withCtx(() => [
222+
\\" \\",
223+
_createVNode(\\"p\\")
224+
]),
225+
_: 1 /* STABLE */
226+
}))
227+
}"
228+
`;
229+
230+
exports[`compiler: transform component slots with whitespace: 'preserve' named default slot + implicit whitespace content 1`] = `
231+
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
232+
233+
return function render(_ctx, _cache) {
234+
const _component_Comp = _resolveComponent(\\"Comp\\")
235+
236+
return (_openBlock(), _createBlock(_component_Comp, null, {
237+
header: _withCtx(() => [\\" Header \\"]),
238+
default: _withCtx(() => [\\" Default \\"]),
239+
_: 1 /* STABLE */
240+
}))
241+
}"
242+
`;
243+
244+
exports[`compiler: transform component slots with whitespace: 'preserve' should not generate whitespace only default slot 1`] = `
245+
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
246+
247+
return function render(_ctx, _cache) {
248+
const _component_Comp = _resolveComponent(\\"Comp\\")
249+
250+
return (_openBlock(), _createBlock(_component_Comp, null, {
251+
header: _withCtx(() => [\\" Header \\"]),
252+
footer: _withCtx(() => [\\" Footer \\"]),
253+
_: 1 /* STABLE */
254+
}))
255+
}"
256+
`;

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

+66-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import {
99
ForNode,
1010
ComponentNode,
1111
VNodeCall,
12-
SlotsExpression
12+
SlotsExpression,
13+
ObjectExpression,
14+
SimpleExpressionNode
1315
} from '../../src'
1416
import { transformElement } from '../../src/transforms/transformElement'
1517
import { transformOn } from '../../src/transforms/vOn'
@@ -27,7 +29,9 @@ import { transformFor } from '../../src/transforms/vFor'
2729
import { transformIf } from '../../src/transforms/vIf'
2830

2931
function parseWithSlots(template: string, options: CompilerOptions = {}) {
30-
const ast = parse(template)
32+
const ast = parse(template, {
33+
whitespace: options.whitespace
34+
})
3135
transform(ast, {
3236
nodeTransforms: [
3337
transformIf,
@@ -862,4 +866,64 @@ describe('compiler: transform component slots', () => {
862866
})
863867
})
864868
})
869+
870+
describe(`with whitespace: 'preserve'`, () => {
871+
test('named default slot + implicit whitespace content', () => {
872+
const source = `
873+
<Comp>
874+
<template #header> Header </template>
875+
<template #default> Default </template>
876+
</Comp>
877+
`
878+
const { root } = parseWithSlots(source, {
879+
whitespace: 'preserve'
880+
})
881+
882+
expect(
883+
`Extraneous children found when component already has explicitly named default slot.`
884+
).not.toHaveBeenWarned()
885+
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
886+
})
887+
888+
test('implicit default slot', () => {
889+
const source = `
890+
<Comp>
891+
<template #header> Header </template>
892+
<p/>
893+
</Comp>
894+
`
895+
const { root } = parseWithSlots(source, {
896+
whitespace: 'preserve'
897+
})
898+
899+
expect(
900+
`Extraneous children found when component already has explicitly named default slot.`
901+
).not.toHaveBeenWarned()
902+
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
903+
})
904+
905+
test('should not generate whitespace only default slot', () => {
906+
const source = `
907+
<Comp>
908+
<template #header> Header </template>
909+
<template #footer> Footer </template>
910+
</Comp>
911+
`
912+
const { root } = parseWithSlots(source, {
913+
whitespace: 'preserve'
914+
})
915+
916+
// slots is vnodeCall's children as an ObjectExpression
917+
const slots = (root as any).children[0].codegenNode.children
918+
.properties as ObjectExpression['properties']
919+
920+
// should be: header, footer, _ (no default)
921+
expect(slots.length).toBe(3)
922+
expect(
923+
slots.some(p => (p.key as SimpleExpressionNode).content === 'default')
924+
).toBe(false)
925+
926+
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
927+
})
928+
})
865929
})

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

+15-1
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,13 @@ export function buildSlots(
311311
if (!hasTemplateSlots) {
312312
// implicit default slot (on component)
313313
slotsProperties.push(buildDefaultSlotProperty(undefined, children))
314-
} else if (implicitDefaultChildren.length) {
314+
} else if (
315+
implicitDefaultChildren.length &&
316+
// #3766
317+
// with whitespace: 'preserve', whitespaces between slots will end up in
318+
// implicitDefaultChildren. Ignore if all implicit children are whitespaces.
319+
implicitDefaultChildren.some(node => isNonWhitespaceContent(node))
320+
) {
315321
// implicit default slot (mixed with named slots)
316322
if (hasNamedDefaultSlot) {
317323
context.onError(
@@ -397,3 +403,11 @@ function hasForwardedSlots(children: TemplateChildNode[]): boolean {
397403
}
398404
return false
399405
}
406+
407+
function isNonWhitespaceContent(node: TemplateChildNode): boolean {
408+
if (node.type !== NodeTypes.TEXT && node.type !== NodeTypes.TEXT_CALL)
409+
return true
410+
return node.type === NodeTypes.TEXT
411+
? !!node.content.trim()
412+
: isNonWhitespaceContent(node.content)
413+
}

0 commit comments

Comments
 (0)