fix(compiler-core): fix whitespace management for slots with whitespace: 'preserve' (#3767)

fix #3766
This commit is contained in:
HcySunYang 2021-05-14 06:24:43 +08:00 committed by GitHub
parent f3d30363ec
commit 47da92146c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 126 additions and 3 deletions

View File

@ -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 */
}))
}"
`;

View File

@ -9,7 +9,9 @@ import {
ForNode, ForNode,
ComponentNode, ComponentNode,
VNodeCall, VNodeCall,
SlotsExpression SlotsExpression,
ObjectExpression,
SimpleExpressionNode
} from '../../src' } from '../../src'
import { transformElement } from '../../src/transforms/transformElement' import { transformElement } from '../../src/transforms/transformElement'
import { transformOn } from '../../src/transforms/vOn' import { transformOn } from '../../src/transforms/vOn'
@ -27,7 +29,9 @@ import { transformFor } from '../../src/transforms/vFor'
import { transformIf } from '../../src/transforms/vIf' import { transformIf } from '../../src/transforms/vIf'
function parseWithSlots(template: string, options: CompilerOptions = {}) { function parseWithSlots(template: string, options: CompilerOptions = {}) {
const ast = parse(template) const ast = parse(template, {
whitespace: options.whitespace
})
transform(ast, { transform(ast, {
nodeTransforms: [ nodeTransforms: [
transformIf, transformIf,
@ -862,4 +866,64 @@ describe('compiler: transform component slots', () => {
}) })
}) })
}) })
describe(`with whitespace: 'preserve'`, () => {
test('named default slot + implicit whitespace content', () => {
const source = `
<Comp>
<template #header> Header </template>
<template #default> Default </template>
</Comp>
`
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 = `
<Comp>
<template #header> Header </template>
<p/>
</Comp>
`
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 = `
<Comp>
<template #header> Header </template>
<template #footer> Footer </template>
</Comp>
`
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()
})
})
}) })

View File

@ -311,7 +311,13 @@ export function buildSlots(
if (!hasTemplateSlots) { if (!hasTemplateSlots) {
// implicit default slot (on component) // implicit default slot (on component)
slotsProperties.push(buildDefaultSlotProperty(undefined, children)) 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) // implicit default slot (mixed with named slots)
if (hasNamedDefaultSlot) { if (hasNamedDefaultSlot) {
context.onError( context.onError(
@ -397,3 +403,11 @@ function hasForwardedSlots(children: TemplateChildNode[]): boolean {
} }
return false 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)
}