_toString(world.burn()),
(_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 }, \\"yes\\")
- : _createBlock(_Fragment, { key: 1 }, \\"no\\")),
+ : _createBlock(_Fragment, { key: 1 }, [
+ \\"no\\"
+ ])),
_createVNode(_Fragment, null, _renderList(list, (value, index) => {
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"span\\", null, _toString(value + index))
toString(_ctx.world.burn()),
(openBlock(), (_ctx.ok)
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
- : createBlock(Fragment, { key: 1 }, \\"no\\")),
+ : createBlock(Fragment, { key: 1 }, [
+ \\"no\\"
+ ])),
createVNode(Fragment, null, renderList(_ctx.list, (value, index) => {
return (openBlock(), createBlock(\\"div\\", null, [
createVNode(\\"span\\", null, toString(value + index))
_toString(_ctx.world.burn()),
(openBlock(), (_ctx.ok)
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
- : createBlock(Fragment, { key: 1 }, \\"no\\")),
+ : createBlock(Fragment, { key: 1 }, [
+ \\"no\\"
+ ])),
createVNode(Fragment, null, renderList(_ctx.list, (value, index) => {
return (openBlock(), createBlock(\\"div\\", null, [
createVNode(\\"span\\", null, _toString(value + index))
}"
`;
+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`] = `
"const _Vue = Vue
}"
`;
+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`] = `
"const _Vue = Vue
? _createBlock(\\"div\\", { key: 0 })
: orNot
? _createBlock(\\"p\\", { key: 1 })
- : _createBlock(_Fragment, { key: 2 }, \\"fine\\"))
+ : _createBlock(_Fragment, { key: 2 }, [
+ \\"fine\\"
+ ]))
}
}"
`;
import { transformFor } from '../../src/transforms/vFor'
import { transformBind } from '../../src/transforms/vBind'
import { transformElement } from '../../src/transforms/transformElement'
+import { transformSlotOutlet } from '../../src/transforms/transfromSlotOutlet'
import { transformExpression } from '../../src/transforms/transformExpression'
import {
ForNode,
CREATE_BLOCK,
FRAGMENT,
RENDER_LIST,
- CREATE_VNODE
+ CREATE_VNODE,
+ RENDER_SLOT
} from '../../src/runtimeConstants'
import { PatchFlags } from '@vue/runtime-dom'
import { PatchFlagNames } from '@vue/shared'
transformIf,
transformFor,
...(options.prefixIdentifiers ? [transformExpression] : []),
+ transformSlotOutlet,
transformElement
],
directiveTransforms: {
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', () => {
const {
root,
import { transform } from '../../src/transform'
import { transformIf } from '../../src/transforms/vIf'
import { transformElement } from '../../src/transforms/transformElement'
+import { transformSlotOutlet } from '../../src/transforms/transfromSlotOutlet'
import {
IfNode,
NodeTypes,
EMPTY,
FRAGMENT,
MERGE_PROPS,
- APPLY_DIRECTIVES
+ APPLY_DIRECTIVES,
+ RENDER_SLOT
} from '../../src/runtimeConstants'
import { createObjectMatcher } from '../testUtils'
) {
const ast = parse(template, options)
transform(ast, {
- nodeTransforms: [transformIf, transformElement],
+ nodeTransforms: [transformIf, transformSlotOutlet, transformElement],
...options
})
if (!options.onError) {
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', () => {
const {
root,
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 {
import {
RootNode,
TemplateChildNode,
- ElementNode,
TextNode,
CommentNode,
ExpressionNode,
InterpolationNode,
CompoundExpressionNode,
SimpleExpressionNode,
- ElementTypes,
FunctionExpression,
SequenceExpression,
ConditionalExpression
// generate the VNode tree expression
push(`return `)
- genChildren(ast.children, context, true)
+
+ genRoot(ast, context)
if (useWithBlock) {
deindent()
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)
}
if (isString(node)) {
push(node)
} else if (isArray(node)) {
- genChildren(node, context)
+ genNodeListAsArray(node, context)
} else {
genNode(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
ElementTypes,
ObjectExpression,
createObjectExpression,
- createObjectProperty
+ createObjectProperty,
+ TemplateChildNode,
+ CallExpression
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { getInnerRange, findProp } from '../utils'
)
])
}
+ 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 {
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 {