mirror of https://github.com/vuejs/core.git
Merge 6b8f1f2778
into ba391f5fdf
This commit is contained in:
commit
46a3e014d9
|
@ -139,6 +139,24 @@ return function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform component slots > named slots w/ implicit default slot containing non-breaking space 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||
|
||||
const _component_Comp = _resolveComponent("Comp")
|
||||
|
||||
return (_openBlock(), _createBlock(_component_Comp, null, {
|
||||
one: _withCtx(() => ["foo"]),
|
||||
default: _withCtx(() => [" "]),
|
||||
_: 1 /* STABLE */
|
||||
}))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform component slots > nested slots scoping 1`] = `
|
||||
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
|
||||
|
||||
|
@ -232,6 +250,20 @@ return function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform component slots > with whitespace: 'preserve' > implicit default slot with non-breaking space 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(() => ["\\n \\n "]),
|
||||
_: 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
|
||||
|
||||
|
@ -268,6 +300,32 @@ return function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform component slots > with whitespace: 'preserve' > named slot with v-if + v-else and comments 1`] = `
|
||||
"const { createTextVNode: _createTextVNode, createCommentVNode: _createCommentVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, createSlots: _createSlots, openBlock: _openBlock, createBlock: _createBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
const _component_Comp = _resolveComponent("Comp")
|
||||
|
||||
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 /* DYNAMIC */ }, [
|
||||
ok
|
||||
? {
|
||||
name: "one",
|
||||
fn: _withCtx(() => [
|
||||
_createTextVNode("foo")
|
||||
]),
|
||||
key: "0"
|
||||
}
|
||||
: {
|
||||
name: "two",
|
||||
fn: _withCtx(() => [
|
||||
_createTextVNode("baz")
|
||||
]),
|
||||
key: "1"
|
||||
}
|
||||
]), 1024 /* DYNAMIC_SLOTS */))
|
||||
}"
|
||||
`;
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
type ForNode,
|
||||
NodeTypes,
|
||||
generate,
|
||||
isWhitespaceText,
|
||||
baseParse as parse,
|
||||
transform,
|
||||
} from '../../src'
|
||||
|
@ -109,6 +110,24 @@ describe('compiler: transform text', () => {
|
|||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('whitespace text', () => {
|
||||
const root = transformWithTextOpt(`<div/>hello<div/> <div/>`)
|
||||
expect(root.children.length).toBe(5)
|
||||
expect(root.children[0].type).toBe(NodeTypes.ELEMENT)
|
||||
expect(root.children[1].type).toBe(NodeTypes.TEXT_CALL)
|
||||
expect(root.children[2].type).toBe(NodeTypes.ELEMENT)
|
||||
expect(root.children[3].type).toBe(NodeTypes.TEXT_CALL)
|
||||
expect(root.children[4].type).toBe(NodeTypes.ELEMENT)
|
||||
|
||||
expect(root.children.map(isWhitespaceText)).toEqual([
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
])
|
||||
})
|
||||
|
||||
test('consecutive text mixed with elements', () => {
|
||||
const root = transformWithTextOpt(
|
||||
`<div/>{{ foo }} bar {{ baz }}<div/>hello<div/>`,
|
||||
|
|
|
@ -246,6 +246,31 @@ describe('compiler: v-if', () => {
|
|||
loc: node3.loc,
|
||||
},
|
||||
])
|
||||
|
||||
const { node: node4 } = parseWithIfTransform(
|
||||
`<div v-if="bar"/>foo<div v-else/>`,
|
||||
{ onError },
|
||||
2,
|
||||
)
|
||||
expect(onError.mock.calls[3]).toMatchObject([
|
||||
{
|
||||
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
|
||||
loc: node4.loc,
|
||||
},
|
||||
])
|
||||
|
||||
// Non-breaking space
|
||||
const { node: node5 } = parseWithIfTransform(
|
||||
`<div v-if="bar"/>\u00a0<div v-else/>`,
|
||||
{ onError },
|
||||
2,
|
||||
)
|
||||
expect(onError.mock.calls[4]).toMatchObject([
|
||||
{
|
||||
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
|
||||
loc: node5.loc,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('error on v-else-if missing adjacent v-if or v-else-if', () => {
|
||||
|
@ -285,6 +310,31 @@ describe('compiler: v-if', () => {
|
|||
},
|
||||
])
|
||||
|
||||
const { node: node4 } = parseWithIfTransform(
|
||||
`<div v-if="bar"/>foo<div v-else-if="foo"/>`,
|
||||
{ onError },
|
||||
2,
|
||||
)
|
||||
expect(onError.mock.calls[3]).toMatchObject([
|
||||
{
|
||||
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
|
||||
loc: node4.loc,
|
||||
},
|
||||
])
|
||||
|
||||
// Non-breaking space
|
||||
const { node: node5 } = parseWithIfTransform(
|
||||
`<div v-if="bar"/>\u00a0<div v-else-if="foo"/>`,
|
||||
{ onError },
|
||||
2,
|
||||
)
|
||||
expect(onError.mock.calls[4]).toMatchObject([
|
||||
{
|
||||
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
|
||||
loc: node5.loc,
|
||||
},
|
||||
])
|
||||
|
||||
const {
|
||||
node: { branches },
|
||||
} = parseWithIfTransform(
|
||||
|
@ -293,7 +343,7 @@ describe('compiler: v-if', () => {
|
|||
0,
|
||||
)
|
||||
|
||||
expect(onError.mock.calls[3]).toMatchObject([
|
||||
expect(onError.mock.calls[5]).toMatchObject([
|
||||
{
|
||||
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
|
||||
loc: branches[branches.length - 1].loc,
|
||||
|
|
|
@ -28,8 +28,12 @@ import { createObjectMatcher } from '../testUtils'
|
|||
import { PatchFlags } from '@vue/shared'
|
||||
import { transformFor } from '../../src/transforms/vFor'
|
||||
import { transformIf } from '../../src/transforms/vIf'
|
||||
import { transformText } from '../../src/transforms/transformText'
|
||||
|
||||
function parseWithSlots(template: string, options: CompilerOptions = {}) {
|
||||
function parseWithSlots(
|
||||
template: string,
|
||||
options: CompilerOptions & { transformText?: boolean } = {},
|
||||
) {
|
||||
const ast = parse(template, {
|
||||
whitespace: options.whitespace,
|
||||
})
|
||||
|
@ -43,6 +47,7 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
|
|||
transformSlotOutlet,
|
||||
transformElement,
|
||||
trackSlotScopes,
|
||||
...(options.transformText ? [transformText] : []),
|
||||
],
|
||||
directiveTransforms: {
|
||||
on: transformOn,
|
||||
|
@ -307,6 +312,40 @@ describe('compiler: transform component slots', () => {
|
|||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('named slots w/ implicit default slot containing non-breaking space', () => {
|
||||
const { root, slots } = parseWithSlots(
|
||||
`<Comp>
|
||||
\u00a0
|
||||
<template #one>foo</template>
|
||||
</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: ` \u00a0 `,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('dynamically named slots', () => {
|
||||
const { root, slots } = parseWithSlots(
|
||||
`<Comp>
|
||||
|
@ -989,6 +1028,27 @@ describe('compiler: transform component slots', () => {
|
|||
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('implicit default slot with non-breaking space', () => {
|
||||
const source = `
|
||||
<Comp>
|
||||
|
||||
<template #header> Header </template>
|
||||
</Comp>
|
||||
`
|
||||
const { root } = parseWithSlots(source, {
|
||||
whitespace: 'preserve',
|
||||
})
|
||||
|
||||
const slots = (root as any).children[0].codegenNode.children
|
||||
.properties as ObjectExpression['properties']
|
||||
|
||||
expect(
|
||||
slots.some(p => (p.key as SimpleExpressionNode).content === 'default'),
|
||||
).toBe(true)
|
||||
|
||||
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('named slot with v-if + v-else', () => {
|
||||
const source = `
|
||||
<Comp>
|
||||
|
@ -1002,5 +1062,23 @@ describe('compiler: transform component slots', () => {
|
|||
|
||||
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('named slot with v-if + v-else and comments', () => {
|
||||
const source = `
|
||||
<Comp>
|
||||
<template #one v-if="ok">foo</template>
|
||||
<!-- start -->
|
||||
|
||||
<!-- end -->
|
||||
<template #two v-else>baz</template>
|
||||
</Comp>
|
||||
`
|
||||
const { root } = parseWithSlots(source, {
|
||||
transformText: true,
|
||||
whitespace: 'preserve',
|
||||
})
|
||||
|
||||
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -40,6 +40,7 @@ import {
|
|||
} from './errors'
|
||||
import {
|
||||
forAliasRE,
|
||||
isAllWhitespace,
|
||||
isCoreComponent,
|
||||
isSimpleIdentifier,
|
||||
isStaticArgOf,
|
||||
|
@ -880,15 +881,6 @@ function condenseWhitespace(nodes: TemplateChildNode[]): TemplateChildNode[] {
|
|||
return removedWhitespace ? nodes.filter(Boolean) : nodes
|
||||
}
|
||||
|
||||
function isAllWhitespace(str: string) {
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
if (!isWhitespace(str.charCodeAt(i))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function hasNewlineChar(str: string) {
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const c = str.charCodeAt(i)
|
||||
|
|
|
@ -32,7 +32,13 @@ import { processExpression } from './transformExpression'
|
|||
import { validateBrowserExpression } from '../validateExpression'
|
||||
import { cloneLoc } from '../parser'
|
||||
import { CREATE_COMMENT, FRAGMENT } from '../runtimeHelpers'
|
||||
import { findDir, findProp, getMemoedVNodeCall, injectProp } from '../utils'
|
||||
import {
|
||||
findDir,
|
||||
findProp,
|
||||
getMemoedVNodeCall,
|
||||
injectProp,
|
||||
isCommentOrWhitespace,
|
||||
} from '../utils'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
|
||||
export const transformIf: NodeTransform = createStructuralDirectiveTransform(
|
||||
|
@ -125,18 +131,11 @@ export function processIf(
|
|||
let i = siblings.indexOf(node)
|
||||
while (i-- >= -1) {
|
||||
const sibling = siblings[i]
|
||||
if (sibling && sibling.type === NodeTypes.COMMENT) {
|
||||
context.removeNode(sibling)
|
||||
__DEV__ && comments.unshift(sibling)
|
||||
continue
|
||||
}
|
||||
|
||||
if (
|
||||
sibling &&
|
||||
sibling.type === NodeTypes.TEXT &&
|
||||
!sibling.content.trim().length
|
||||
) {
|
||||
if (sibling && isCommentOrWhitespace(sibling)) {
|
||||
context.removeNode(sibling)
|
||||
if (__DEV__ && sibling.type === NodeTypes.COMMENT) {
|
||||
comments.unshift(sibling)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -26,9 +26,11 @@ import {
|
|||
assert,
|
||||
findDir,
|
||||
hasScopeRef,
|
||||
isCommentOrWhitespace,
|
||||
isStaticExp,
|
||||
isTemplateNode,
|
||||
isVSlot,
|
||||
isWhitespaceText,
|
||||
} from '../utils'
|
||||
import { CREATE_SLOTS, RENDER_LIST, WITH_CTX } from '../runtimeHelpers'
|
||||
import { createForLoopParams, finalizeForParseResult } from './vFor'
|
||||
|
@ -222,7 +224,7 @@ export function buildSlots(
|
|||
let prev
|
||||
while (j--) {
|
||||
prev = children[j]
|
||||
if (prev.type !== NodeTypes.COMMENT && isNonWhitespaceContent(prev)) {
|
||||
if (!isCommentOrWhitespace(prev)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -319,7 +321,7 @@ export function buildSlots(
|
|||
// #3766
|
||||
// with whitespace: 'preserve', whitespaces between slots will end up in
|
||||
// implicitDefaultChildren. Ignore if all implicit children are whitespaces.
|
||||
implicitDefaultChildren.some(node => isNonWhitespaceContent(node))
|
||||
!implicitDefaultChildren.every(isWhitespaceText)
|
||||
) {
|
||||
// implicit default slot (mixed with named slots)
|
||||
if (hasNamedDefaultSlot) {
|
||||
|
@ -411,11 +413,3 @@ 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)
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ import type { PropsExpression } from './transforms/transformElement'
|
|||
import { parseExpression } from '@babel/parser'
|
||||
import type { Expression, Node } from '@babel/types'
|
||||
import { unwrapTSNode } from './babelUtils'
|
||||
import { isWhitespace } from './tokenizer'
|
||||
|
||||
export const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
|
||||
p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic
|
||||
|
@ -564,3 +565,23 @@ export function getMemoedVNodeCall(
|
|||
}
|
||||
|
||||
export const forAliasRE: RegExp = /([\s\S]*?)\s+(?:in|of)\s+(\S[\s\S]*)/
|
||||
|
||||
export function isAllWhitespace(str: string): boolean {
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
if (!isWhitespace(str.charCodeAt(i))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export function isWhitespaceText(node: TemplateChildNode): boolean {
|
||||
return (
|
||||
(node.type === NodeTypes.TEXT && isAllWhitespace(node.content)) ||
|
||||
(node.type === NodeTypes.TEXT_CALL && isWhitespaceText(node.content))
|
||||
)
|
||||
}
|
||||
|
||||
export function isCommentOrWhitespace(node: TemplateChildNode): boolean {
|
||||
return node.type === NodeTypes.COMMENT || isWhitespaceText(node)
|
||||
}
|
||||
|
|
|
@ -135,6 +135,18 @@ describe('Transition multi children warnings', () => {
|
|||
false,
|
||||
)
|
||||
})
|
||||
|
||||
test('non-breaking spaces are treated as normal text', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
\u00a0
|
||||
<div>foo</div>
|
||||
</transition>
|
||||
`,
|
||||
true,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test('inject persisted when child has v-show', () => {
|
||||
|
@ -164,3 +176,19 @@ test('the v-if/else-if/else branches in Transition should ignore comments', () =
|
|||
`).code,
|
||||
).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('comments and preserved whitespace are ignored', () => {
|
||||
expect(
|
||||
compile(
|
||||
`
|
||||
<transition>
|
||||
<!-- foo --> <!-- bar -->
|
||||
<div>foo bar</div>
|
||||
</transition>
|
||||
`,
|
||||
{
|
||||
whitespace: 'preserve',
|
||||
},
|
||||
).code,
|
||||
).toMatchSnapshot()
|
||||
})
|
||||
|
|
|
@ -1,5 +1,22 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`comments and preserved whitespace are ignored 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, Transition: _Transition, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createBlock(_Transition, null, {
|
||||
default: _withCtx(() => [
|
||||
_createElementVNode("div", null, "foo bar")
|
||||
]),
|
||||
_: 1 /* STABLE */
|
||||
}))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`inject persisted when child has v-show 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
type IfBranchNode,
|
||||
type NodeTransform,
|
||||
NodeTypes,
|
||||
isCommentOrWhitespace,
|
||||
} from '@vue/compiler-core'
|
||||
import { TRANSITION } from '../runtimeHelpers'
|
||||
import { DOMErrorCodes, createDOMCompilerError } from '../errors'
|
||||
|
@ -56,11 +57,9 @@ export const transformTransition: NodeTransform = (node, context) => {
|
|||
}
|
||||
|
||||
function hasMultipleChildren(node: ComponentNode | IfBranchNode): boolean {
|
||||
// #1352 filter out potential comment nodes.
|
||||
// filter out potential comment nodes (#1352) and whitespace (#4637)
|
||||
const children = (node.children = node.children.filter(
|
||||
c =>
|
||||
c.type !== NodeTypes.COMMENT &&
|
||||
!(c.type === NodeTypes.TEXT && !c.content.trim()),
|
||||
c => !isCommentOrWhitespace(c),
|
||||
))
|
||||
const child = children[0]
|
||||
return (
|
||||
|
|
Loading…
Reference in New Issue