fix(compiler): generate correct fragment children when it contains single text node or slot outlet
This commit is contained in:
parent
a477594d65
commit
3a95a2f148
@ -14,7 +14,9 @@ return function render() {
|
|||||||
_toString(world.burn()),
|
_toString(world.burn()),
|
||||||
(_openBlock(), ok
|
(_openBlock(), ok
|
||||||
? _createBlock(\\"div\\", { key: 0 }, \\"yes\\")
|
? _createBlock(\\"div\\", { key: 0 }, \\"yes\\")
|
||||||
: _createBlock(_Fragment, { key: 1 }, \\"no\\")),
|
: _createBlock(_Fragment, { key: 1 }, [
|
||||||
|
\\"no\\"
|
||||||
|
])),
|
||||||
_createVNode(_Fragment, null, _renderList(list, (value, index) => {
|
_createVNode(_Fragment, null, _renderList(list, (value, index) => {
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
_createVNode(\\"span\\", null, _toString(value + index))
|
_createVNode(\\"span\\", null, _toString(value + index))
|
||||||
@ -37,7 +39,9 @@ return function render() {
|
|||||||
toString(_ctx.world.burn()),
|
toString(_ctx.world.burn()),
|
||||||
(openBlock(), (_ctx.ok)
|
(openBlock(), (_ctx.ok)
|
||||||
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
|
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
|
||||||
: createBlock(Fragment, { key: 1 }, \\"no\\")),
|
: createBlock(Fragment, { key: 1 }, [
|
||||||
|
\\"no\\"
|
||||||
|
])),
|
||||||
createVNode(Fragment, null, renderList(_ctx.list, (value, index) => {
|
createVNode(Fragment, null, renderList(_ctx.list, (value, index) => {
|
||||||
return (openBlock(), createBlock(\\"div\\", null, [
|
return (openBlock(), createBlock(\\"div\\", null, [
|
||||||
createVNode(\\"span\\", null, toString(value + index))
|
createVNode(\\"span\\", null, toString(value + index))
|
||||||
@ -59,7 +63,9 @@ export default function render() {
|
|||||||
_toString(_ctx.world.burn()),
|
_toString(_ctx.world.burn()),
|
||||||
(openBlock(), (_ctx.ok)
|
(openBlock(), (_ctx.ok)
|
||||||
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
|
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
|
||||||
: createBlock(Fragment, { key: 1 }, \\"no\\")),
|
: createBlock(Fragment, { key: 1 }, [
|
||||||
|
\\"no\\"
|
||||||
|
])),
|
||||||
createVNode(Fragment, null, renderList(_ctx.list, (value, index) => {
|
createVNode(Fragment, null, renderList(_ctx.list, (value, index) => {
|
||||||
return (openBlock(), createBlock(\\"div\\", null, [
|
return (openBlock(), createBlock(\\"div\\", null, [
|
||||||
createVNode(\\"span\\", null, _toString(value + index))
|
createVNode(\\"span\\", null, _toString(value + index))
|
||||||
|
@ -104,6 +104,20 @@ return function render() {
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-for codegen template v-for w/ <slot/> 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render() {
|
||||||
|
with (this) {
|
||||||
|
const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, renderSlot: _renderSlot, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
|
return _createVNode(_Fragment, null, _renderList(items, (item) => {
|
||||||
|
return (_openBlock(), _createBlock(_Fragment, null, _renderSlot($slots.default)))
|
||||||
|
}), 128 /* UNKEYED_FRAGMENT */)
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`compiler: v-for codegen v-if + v-for 1`] = `
|
exports[`compiler: v-for codegen v-if + v-for 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
@ -32,6 +32,20 @@ return function render() {
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-if codegen template v-if w/ single <slot/> child 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render() {
|
||||||
|
with (this) {
|
||||||
|
const { openBlock: _openBlock, renderSlot: _renderSlot, Fragment: _Fragment, createBlock: _createBlock, Empty: _Empty } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(), ok
|
||||||
|
? _createBlock(_Fragment, { key: 0 }, _renderSlot($slots.default))
|
||||||
|
: _createBlock(_Empty))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`compiler: v-if codegen v-if + v-else 1`] = `
|
exports[`compiler: v-if codegen v-if + v-else 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
@ -57,7 +71,9 @@ return function render() {
|
|||||||
? _createBlock(\\"div\\", { key: 0 })
|
? _createBlock(\\"div\\", { key: 0 })
|
||||||
: orNot
|
: orNot
|
||||||
? _createBlock(\\"p\\", { key: 1 })
|
? _createBlock(\\"p\\", { key: 1 })
|
||||||
: _createBlock(_Fragment, { key: 2 }, \\"fine\\"))
|
: _createBlock(_Fragment, { key: 2 }, [
|
||||||
|
\\"fine\\"
|
||||||
|
]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -4,6 +4,7 @@ import { transformIf } from '../../src/transforms/vIf'
|
|||||||
import { transformFor } from '../../src/transforms/vFor'
|
import { transformFor } from '../../src/transforms/vFor'
|
||||||
import { transformBind } from '../../src/transforms/vBind'
|
import { transformBind } from '../../src/transforms/vBind'
|
||||||
import { transformElement } from '../../src/transforms/transformElement'
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
|
import { transformSlotOutlet } from '../../src/transforms/transfromSlotOutlet'
|
||||||
import { transformExpression } from '../../src/transforms/transformExpression'
|
import { transformExpression } from '../../src/transforms/transformExpression'
|
||||||
import {
|
import {
|
||||||
ForNode,
|
ForNode,
|
||||||
@ -20,7 +21,8 @@ import {
|
|||||||
CREATE_BLOCK,
|
CREATE_BLOCK,
|
||||||
FRAGMENT,
|
FRAGMENT,
|
||||||
RENDER_LIST,
|
RENDER_LIST,
|
||||||
CREATE_VNODE
|
CREATE_VNODE,
|
||||||
|
RENDER_SLOT
|
||||||
} from '../../src/runtimeConstants'
|
} from '../../src/runtimeConstants'
|
||||||
import { PatchFlags } from '@vue/runtime-dom'
|
import { PatchFlags } from '@vue/runtime-dom'
|
||||||
import { PatchFlagNames } from '@vue/shared'
|
import { PatchFlagNames } from '@vue/shared'
|
||||||
@ -36,6 +38,7 @@ function parseWithForTransform(
|
|||||||
transformIf,
|
transformIf,
|
||||||
transformFor,
|
transformFor,
|
||||||
...(options.prefixIdentifiers ? [transformExpression] : []),
|
...(options.prefixIdentifiers ? [transformExpression] : []),
|
||||||
|
transformSlotOutlet,
|
||||||
transformElement
|
transformElement
|
||||||
],
|
],
|
||||||
directiveTransforms: {
|
directiveTransforms: {
|
||||||
@ -692,6 +695,28 @@ describe('compiler: v-for', () => {
|
|||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('template v-for w/ <slot/>', () => {
|
||||||
|
const {
|
||||||
|
root,
|
||||||
|
node: { codegenNode }
|
||||||
|
} = parseWithForTransform(
|
||||||
|
'<template v-for="item in items"><slot/></template>'
|
||||||
|
)
|
||||||
|
expect(assertSharedCodegen(codegenNode)).toMatchObject({
|
||||||
|
source: { content: `items` },
|
||||||
|
params: [{ content: `item` }],
|
||||||
|
blockArgs: [
|
||||||
|
`_${FRAGMENT}`,
|
||||||
|
`null`,
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: `_${RENDER_SLOT}`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
test('keyed v-for', () => {
|
test('keyed v-for', () => {
|
||||||
const {
|
const {
|
||||||
root,
|
root,
|
||||||
|
@ -2,6 +2,7 @@ import { parse } from '../../src/parse'
|
|||||||
import { transform } from '../../src/transform'
|
import { transform } from '../../src/transform'
|
||||||
import { transformIf } from '../../src/transforms/vIf'
|
import { transformIf } from '../../src/transforms/vIf'
|
||||||
import { transformElement } from '../../src/transforms/transformElement'
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
|
import { transformSlotOutlet } from '../../src/transforms/transfromSlotOutlet'
|
||||||
import {
|
import {
|
||||||
IfNode,
|
IfNode,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
@ -21,7 +22,8 @@ import {
|
|||||||
EMPTY,
|
EMPTY,
|
||||||
FRAGMENT,
|
FRAGMENT,
|
||||||
MERGE_PROPS,
|
MERGE_PROPS,
|
||||||
APPLY_DIRECTIVES
|
APPLY_DIRECTIVES,
|
||||||
|
RENDER_SLOT
|
||||||
} from '../../src/runtimeConstants'
|
} from '../../src/runtimeConstants'
|
||||||
import { createObjectMatcher } from '../testUtils'
|
import { createObjectMatcher } from '../testUtils'
|
||||||
|
|
||||||
@ -32,7 +34,7 @@ function parseWithIfTransform(
|
|||||||
) {
|
) {
|
||||||
const ast = parse(template, options)
|
const ast = parse(template, options)
|
||||||
transform(ast, {
|
transform(ast, {
|
||||||
nodeTransforms: [transformIf, transformElement],
|
nodeTransforms: [transformIf, transformSlotOutlet, transformElement],
|
||||||
...options
|
...options
|
||||||
})
|
})
|
||||||
if (!options.onError) {
|
if (!options.onError) {
|
||||||
@ -344,6 +346,25 @@ describe('compiler: v-if', () => {
|
|||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('template v-if w/ single <slot/> child', () => {
|
||||||
|
const {
|
||||||
|
root,
|
||||||
|
node: { codegenNode }
|
||||||
|
} = parseWithIfTransform(`<template v-if="ok"><slot/></template>`)
|
||||||
|
assertSharedCodegen(codegenNode)
|
||||||
|
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
|
||||||
|
.consequent as CallExpression
|
||||||
|
expect(branch1.arguments).toMatchObject([
|
||||||
|
`_${FRAGMENT}`,
|
||||||
|
`{ key: 0 }`,
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: `_${RENDER_SLOT}`
|
||||||
|
}
|
||||||
|
])
|
||||||
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
test('v-if + v-else', () => {
|
test('v-if + v-else', () => {
|
||||||
const {
|
const {
|
||||||
root,
|
root,
|
||||||
|
@ -176,7 +176,7 @@ export type JSChildNode =
|
|||||||
export interface CallExpression extends Node {
|
export interface CallExpression extends Node {
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION
|
type: NodeTypes.JS_CALL_EXPRESSION
|
||||||
callee: string
|
callee: string
|
||||||
arguments: (string | JSChildNode | TemplateChildNode[])[]
|
arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ObjectExpression extends Node {
|
export interface ObjectExpression extends Node {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
RootNode,
|
RootNode,
|
||||||
TemplateChildNode,
|
TemplateChildNode,
|
||||||
ElementNode,
|
|
||||||
TextNode,
|
TextNode,
|
||||||
CommentNode,
|
CommentNode,
|
||||||
ExpressionNode,
|
ExpressionNode,
|
||||||
@ -15,7 +14,6 @@ import {
|
|||||||
InterpolationNode,
|
InterpolationNode,
|
||||||
CompoundExpressionNode,
|
CompoundExpressionNode,
|
||||||
SimpleExpressionNode,
|
SimpleExpressionNode,
|
||||||
ElementTypes,
|
|
||||||
FunctionExpression,
|
FunctionExpression,
|
||||||
SequenceExpression,
|
SequenceExpression,
|
||||||
ConditionalExpression
|
ConditionalExpression
|
||||||
@ -232,7 +230,8 @@ export function generate(
|
|||||||
|
|
||||||
// generate the VNode tree expression
|
// generate the VNode tree expression
|
||||||
push(`return `)
|
push(`return `)
|
||||||
genChildren(ast.children, context, true)
|
|
||||||
|
genRoot(ast, context)
|
||||||
|
|
||||||
if (useWithBlock) {
|
if (useWithBlock) {
|
||||||
deindent()
|
deindent()
|
||||||
@ -257,31 +256,13 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
|
|||||||
context.newline()
|
context.newline()
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will generate a single vnode call if:
|
function genRoot(root: RootNode, context: CodegenContext) {
|
||||||
// - The target position explicitly allows a single node (root, if, for)
|
// TODO handle blocks
|
||||||
// - The list has length === 1, AND The only child is a:
|
const { children } = root
|
||||||
// - text
|
if (children.length === 0) {
|
||||||
// - <slot> outlet, which always produces an array
|
context.push(`null`)
|
||||||
function genChildren(
|
} else if (children.length === 1) {
|
||||||
children: TemplateChildNode[],
|
genNode(children[0], context)
|
||||||
context: CodegenContext,
|
|
||||||
allowSingle: boolean = false
|
|
||||||
) {
|
|
||||||
if (!children.length) {
|
|
||||||
return context.push(`null`)
|
|
||||||
}
|
|
||||||
const child = children[0]
|
|
||||||
const type = child.type
|
|
||||||
if (
|
|
||||||
children.length === 1 &&
|
|
||||||
(allowSingle ||
|
|
||||||
type === NodeTypes.TEXT ||
|
|
||||||
type === NodeTypes.INTERPOLATION ||
|
|
||||||
type === NodeTypes.COMPOUND_EXPRESSION ||
|
|
||||||
(type === NodeTypes.ELEMENT &&
|
|
||||||
(child as ElementNode).tagType === ElementTypes.SLOT))
|
|
||||||
) {
|
|
||||||
genNode(child, context)
|
|
||||||
} else {
|
} else {
|
||||||
genNodeListAsArray(children, context)
|
genNodeListAsArray(children, context)
|
||||||
}
|
}
|
||||||
@ -316,7 +297,7 @@ function genNodeList(
|
|||||||
if (isString(node)) {
|
if (isString(node)) {
|
||||||
push(node)
|
push(node)
|
||||||
} else if (isArray(node)) {
|
} else if (isArray(node)) {
|
||||||
genChildren(node, context)
|
genNodeListAsArray(node, context)
|
||||||
} else {
|
} else {
|
||||||
genNode(node, context)
|
genNode(node, context)
|
||||||
}
|
}
|
||||||
|
@ -87,8 +87,26 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
patchFlag |= PatchFlags.DYNAMIC_SLOTS
|
patchFlag |= PatchFlags.DYNAMIC_SLOTS
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// only v-for fragments will have keyed/unkeyed flags
|
if (node.children.length === 1) {
|
||||||
args.push(node.children)
|
const child = node.children[0]
|
||||||
|
const type = child.type
|
||||||
|
// pass directly if the only child is one of:
|
||||||
|
// - text (plain / interpolation / expression)
|
||||||
|
// - <slot> outlet (already an array)
|
||||||
|
if (
|
||||||
|
type === NodeTypes.TEXT ||
|
||||||
|
type === NodeTypes.INTERPOLATION ||
|
||||||
|
type === NodeTypes.COMPOUND_EXPRESSION ||
|
||||||
|
(type === NodeTypes.ELEMENT &&
|
||||||
|
(child as ElementNode).tagType === ElementTypes.SLOT)
|
||||||
|
) {
|
||||||
|
args.push(child)
|
||||||
|
} else {
|
||||||
|
args.push(node.children)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
args.push(node.children)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// patchFlag & dynamicPropNames
|
// patchFlag & dynamicPropNames
|
||||||
|
@ -14,7 +14,9 @@ import {
|
|||||||
ElementTypes,
|
ElementTypes,
|
||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
createObjectExpression,
|
createObjectExpression,
|
||||||
createObjectProperty
|
createObjectProperty,
|
||||||
|
TemplateChildNode,
|
||||||
|
CallExpression
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import { getInnerRange, findProp } from '../utils'
|
import { getInnerRange, findProp } from '../utils'
|
||||||
@ -120,12 +122,23 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||||||
)
|
)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
let childBlockChildren: TemplateChildNode[] | CallExpression =
|
||||||
|
node.children
|
||||||
|
if (childBlockChildren.length === 1) {
|
||||||
|
const child = childBlockChildren[0]
|
||||||
|
if (
|
||||||
|
child.type === NodeTypes.ELEMENT &&
|
||||||
|
child.tagType === ElementTypes.SLOT
|
||||||
|
) {
|
||||||
|
childBlockChildren = child.codegenNode!
|
||||||
|
}
|
||||||
|
}
|
||||||
childBlock = createSequenceExpression([
|
childBlock = createSequenceExpression([
|
||||||
createCallExpression(helper(OPEN_BLOCK)),
|
createCallExpression(helper(OPEN_BLOCK)),
|
||||||
createCallExpression(helper(CREATE_BLOCK), [
|
createCallExpression(helper(CREATE_BLOCK), [
|
||||||
helper(FRAGMENT),
|
helper(FRAGMENT),
|
||||||
childBlockProps,
|
childBlockProps,
|
||||||
node.children
|
childBlockChildren
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
|
@ -164,19 +164,30 @@ function createChildrenCodegenNode(
|
|||||||
const { children } = branch
|
const { children } = branch
|
||||||
const child = children[0]
|
const child = children[0]
|
||||||
const needFragmentWrapper =
|
const needFragmentWrapper =
|
||||||
children.length > 1 || child.type !== NodeTypes.ELEMENT
|
children.length !== 1 ||
|
||||||
|
child.type !== NodeTypes.ELEMENT ||
|
||||||
|
child.tagType === ElementTypes.SLOT
|
||||||
if (needFragmentWrapper) {
|
if (needFragmentWrapper) {
|
||||||
const blockArgs: CallExpression['arguments'] = [
|
const blockArgs: CallExpression['arguments'] = [
|
||||||
helper(FRAGMENT),
|
helper(FRAGMENT),
|
||||||
keyExp,
|
keyExp,
|
||||||
children
|
children
|
||||||
]
|
]
|
||||||
// optimize away nested fragments when child is a ForNode
|
if (children.length === 1) {
|
||||||
if (children.length === 1 && child.type === NodeTypes.FOR) {
|
// optimize away nested fragments when child is a ForNode
|
||||||
const forBlockExp = child.codegenNode
|
if (child.type === NodeTypes.FOR) {
|
||||||
// directly use the for block's children and patchFlag
|
const forBlockArgs = child.codegenNode.arguments
|
||||||
blockArgs[2] = forBlockExp.arguments[2]
|
// directly use the for block's children and patchFlag
|
||||||
blockArgs[3] = forBlockExp.arguments[3]
|
blockArgs[2] = forBlockArgs[2]
|
||||||
|
blockArgs[3] = forBlockArgs[3]
|
||||||
|
} else if (
|
||||||
|
child.type === NodeTypes.ELEMENT &&
|
||||||
|
child.tagType === ElementTypes.SLOT
|
||||||
|
) {
|
||||||
|
// <template v-if="..."><slot/></template>
|
||||||
|
// since slot always returns array, use it directly as the fragment children.
|
||||||
|
blockArgs[2] = child.codegenNode!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return createCallExpression(helper(CREATE_BLOCK), blockArgs)
|
return createCallExpression(helper(CREATE_BLOCK), blockArgs)
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user