fix(compiler): generate correct fragment children when it contains single text node or slot outlet

This commit is contained in:
Evan You
2019-10-01 23:53:52 -04:00
parent a477594d65
commit 3a95a2f148
10 changed files with 153 additions and 48 deletions

View File

@@ -176,7 +176,7 @@ export type JSChildNode =
export interface CallExpression extends Node {
type: NodeTypes.JS_CALL_EXPRESSION
callee: string
arguments: (string | JSChildNode | TemplateChildNode[])[]
arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[]
}
export interface ObjectExpression extends Node {

View File

@@ -1,7 +1,6 @@
import {
RootNode,
TemplateChildNode,
ElementNode,
TextNode,
CommentNode,
ExpressionNode,
@@ -15,7 +14,6 @@ import {
InterpolationNode,
CompoundExpressionNode,
SimpleExpressionNode,
ElementTypes,
FunctionExpression,
SequenceExpression,
ConditionalExpression
@@ -232,7 +230,8 @@ export function generate(
// generate the VNode tree expression
push(`return `)
genChildren(ast.children, context, true)
genRoot(ast, context)
if (useWithBlock) {
deindent()
@@ -257,31 +256,13 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
context.newline()
}
// This will generate a single vnode call if:
// - The target position explicitly allows a single node (root, if, for)
// - The list has length === 1, AND The only child is a:
// - text
// - <slot> outlet, which always produces an array
function genChildren(
children: TemplateChildNode[],
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)
function genRoot(root: RootNode, context: CodegenContext) {
// TODO handle blocks
const { children } = root
if (children.length === 0) {
context.push(`null`)
} else if (children.length === 1) {
genNode(children[0], context)
} else {
genNodeListAsArray(children, context)
}
@@ -316,7 +297,7 @@ function genNodeList(
if (isString(node)) {
push(node)
} else if (isArray(node)) {
genChildren(node, context)
genNodeListAsArray(node, context)
} else {
genNode(node, context)
}

View File

@@ -87,8 +87,26 @@ export const transformElement: NodeTransform = (node, context) => {
patchFlag |= PatchFlags.DYNAMIC_SLOTS
}
} else {
// only v-for fragments will have keyed/unkeyed flags
args.push(node.children)
if (node.children.length === 1) {
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

View File

@@ -14,7 +14,9 @@ import {
ElementTypes,
ObjectExpression,
createObjectExpression,
createObjectProperty
createObjectProperty,
TemplateChildNode,
CallExpression
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
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([
createCallExpression(helper(OPEN_BLOCK)),
createCallExpression(helper(CREATE_BLOCK), [
helper(FRAGMENT),
childBlockProps,
node.children
childBlockChildren
])
])
} else {

View File

@@ -164,19 +164,30 @@ function createChildrenCodegenNode(
const { children } = branch
const child = children[0]
const needFragmentWrapper =
children.length > 1 || child.type !== NodeTypes.ELEMENT
children.length !== 1 ||
child.type !== NodeTypes.ELEMENT ||
child.tagType === ElementTypes.SLOT
if (needFragmentWrapper) {
const blockArgs: CallExpression['arguments'] = [
helper(FRAGMENT),
keyExp,
children
]
// optimize away nested fragments when child is a ForNode
if (children.length === 1 && child.type === NodeTypes.FOR) {
const forBlockExp = child.codegenNode
// directly use the for block's children and patchFlag
blockArgs[2] = forBlockExp.arguments[2]
blockArgs[3] = forBlockExp.arguments[3]
if (children.length === 1) {
// optimize away nested fragments when child is a ForNode
if (child.type === NodeTypes.FOR) {
const forBlockArgs = child.codegenNode.arguments
// directly use the for block's children and patchFlag
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)
} else {