diff --git a/packages/compiler-core/__tests__/transform.spec.ts b/packages/compiler-core/__tests__/transform.spec.ts
index aa04c3f0..44b88c93 100644
--- a/packages/compiler-core/__tests__/transform.spec.ts
+++ b/packages/compiler-core/__tests__/transform.spec.ts
@@ -274,16 +274,13 @@ describe('compiler: transform', () => {
test('single ', () => {
const ast = transformWithCodegen(``)
- expect(ast.codegenNode).toMatchObject(
- createBlockMatcher([
- `_${FRAGMENT}`,
- `null`,
- {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: `_${RENDER_SLOT}`
- }
- ])
- )
+ expect(ast.codegenNode).toMatchObject({
+ codegenNode: {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: `_${RENDER_SLOT}`,
+ arguments: ['$slots.default']
+ }
+ })
})
test('single element', () => {
diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
index c2df6a12..d40121be 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
@@ -112,7 +112,21 @@ return function render() {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, renderSlot: _renderSlot } = _Vue
return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (item) => {
- return (_openBlock(), _createBlock(_Fragment, null, _renderSlot($slots.default)))
+ return _renderSlot($slots.default)
+ }), 128 /* UNKEYED_FRAGMENT */))
+ }
+}"
+`;
+
+exports[`compiler: v-for codegen v-for on 1`] = `
+"const _Vue = Vue
+
+return function render() {
+ with (this) {
+ const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, renderSlot: _renderSlot } = _Vue
+
+ return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (item) => {
+ return _renderSlot($slots.default)
}), 128 /* UNKEYED_FRAGMENT */))
}
}"
diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap
index 3291e8dc..8fd74760 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap
@@ -37,10 +37,10 @@ exports[`compiler: v-if codegen template v-if w/ single child 1`] = `
return function render() {
with (this) {
- const { openBlock: _openBlock, renderSlot: _renderSlot, Fragment: _Fragment, createBlock: _createBlock, Empty: _Empty } = _Vue
+ const { openBlock: _openBlock, renderSlot: _renderSlot, createBlock: _createBlock, Empty: _Empty } = _Vue
return (_openBlock(), ok
- ? _createBlock(_Fragment, { key: 0 }, _renderSlot($slots.default))
+ ? _renderSlot($slots.default, { key: 0 })
: _createBlock(_Empty))
}
}"
diff --git a/packages/compiler-core/__tests__/transforms/vFor.spec.ts b/packages/compiler-core/__tests__/transforms/vFor.spec.ts
index 0357443c..0bb39161 100644
--- a/packages/compiler-core/__tests__/transforms/vFor.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vFor.spec.ts
@@ -567,7 +567,8 @@ describe('compiler: v-for', () => {
describe('codegen', () => {
function assertSharedCodegen(
node: SequenceExpression,
- keyed: boolean = false
+ keyed: boolean = false,
+ customReturn: boolean = false
) {
expect(node).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
@@ -589,19 +590,21 @@ describe('compiler: v-for', () => {
{}, // to be asserted by each test
{
type: NodeTypes.JS_FUNCTION_EXPRESSION,
- returns: {
- type: NodeTypes.JS_SEQUENCE_EXPRESSION,
- expressions: [
- {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: `_${OPEN_BLOCK}`
- },
- {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: `_${CREATE_BLOCK}`
+ returns: customReturn
+ ? {}
+ : {
+ type: NodeTypes.JS_SEQUENCE_EXPRESSION,
+ expressions: [
+ {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: `_${OPEN_BLOCK}`
+ },
+ {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: `_${CREATE_BLOCK}`
+ }
+ ]
}
- ]
- }
}
]
},
@@ -621,7 +624,10 @@ describe('compiler: v-for', () => {
return {
source: renderListArgs[0] as SimpleExpressionNode,
params: (renderListArgs[1] as any).params,
- blockArgs: (renderListArgs[1] as any).returns.expressions[1].arguments
+ returns: (renderListArgs[1] as any).returns,
+ blockArgs: customReturn
+ ? null
+ : (renderListArgs[1] as any).returns.expressions[1].arguments
}
}
@@ -715,17 +721,33 @@ describe('compiler: v-for', () => {
} = parseWithForTransform(
''
)
- expect(assertSharedCodegen(codegenNode)).toMatchObject({
+ expect(
+ assertSharedCodegen(codegenNode, false, true /* custom return */)
+ ).toMatchObject({
source: { content: `items` },
params: [{ content: `item` }],
- blockArgs: [
- `_${FRAGMENT}`,
- `null`,
- {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: `_${RENDER_SLOT}`
- }
- ]
+ returns: {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: `_${RENDER_SLOT}`
+ }
+ })
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('v-for on ', () => {
+ const {
+ root,
+ node: { codegenNode }
+ } = parseWithForTransform('')
+ expect(
+ assertSharedCodegen(codegenNode, false, true /* custom return */)
+ ).toMatchObject({
+ source: { content: `items` },
+ params: [{ content: `item` }],
+ returns: {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: `_${RENDER_SLOT}`
+ }
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -794,7 +816,7 @@ describe('compiler: v-for', () => {
// should optimize v-if + v-for into a single Fragment block
arguments: [
`_${FRAGMENT}`,
- `{ key: 0 }`,
+ createObjectMatcher({ key: `[0]` }),
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_LIST}`,
diff --git a/packages/compiler-core/__tests__/transforms/vIf.spec.ts b/packages/compiler-core/__tests__/transforms/vIf.spec.ts
index 1010e914..930313c3 100644
--- a/packages/compiler-core/__tests__/transforms/vIf.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vIf.spec.ts
@@ -316,7 +316,10 @@ describe('compiler: v-if', () => {
assertSharedCodegen(codegenNode)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
- expect(branch1.arguments).toMatchObject([`"div"`, `{ key: 0 }`])
+ expect(branch1.arguments).toMatchObject([
+ `"div"`,
+ createObjectMatcher({ key: `[0]` })
+ ])
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
.alternate as CallExpression
expect(branch2.arguments).toMatchObject([`_${EMPTY}`])
@@ -333,7 +336,7 @@ describe('compiler: v-if', () => {
.consequent as CallExpression
expect(branch1.arguments).toMatchObject([
`_${FRAGMENT}`,
- `{ key: 0 }`,
+ createObjectMatcher({ key: `[0]` }),
[
{ type: NodeTypes.ELEMENT, tag: 'div' },
{ type: NodeTypes.TEXT, content: `hello` },
@@ -351,17 +354,14 @@ describe('compiler: v-if', () => {
root,
node: { codegenNode }
} = parseWithIfTransform(``)
- assertSharedCodegen(codegenNode)
+ // 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(branch1).toMatchObject({
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: `_${RENDER_SLOT}`,
+ arguments: ['$slots.default', createObjectMatcher({ key: `[0]` })]
+ })
expect(generate(root).code).toMatchSnapshot()
})
@@ -373,10 +373,16 @@ describe('compiler: v-if', () => {
assertSharedCodegen(codegenNode)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
- expect(branch1.arguments).toMatchObject([`"div"`, `{ key: 0 }`])
+ expect(branch1.arguments).toMatchObject([
+ `"div"`,
+ createObjectMatcher({ key: `[0]` })
+ ])
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
.alternate as CallExpression
- expect(branch2.arguments).toMatchObject([`"p"`, `{ key: 1 }`])
+ expect(branch2.arguments).toMatchObject([
+ `"p"`,
+ createObjectMatcher({ key: `[1]` })
+ ])
expect(generate(root).code).toMatchSnapshot()
})
@@ -388,12 +394,15 @@ describe('compiler: v-if', () => {
assertSharedCodegen(codegenNode, 1)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
- expect(branch1.arguments).toMatchObject([`"div"`, `{ key: 0 }`])
+ expect(branch1.arguments).toMatchObject([
+ `"div"`,
+ createObjectMatcher({ key: `[0]` })
+ ])
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
.alternate as ConditionalExpression
expect((branch2.consequent as CallExpression).arguments).toMatchObject([
`"p"`,
- `{ key: 1 }`
+ createObjectMatcher({ key: `[1]` })
])
expect(generate(root).code).toMatchSnapshot()
})
@@ -408,16 +417,19 @@ describe('compiler: v-if', () => {
assertSharedCodegen(codegenNode, 1)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
- expect(branch1.arguments).toMatchObject([`"div"`, `{ key: 0 }`])
+ expect(branch1.arguments).toMatchObject([
+ `"div"`,
+ createObjectMatcher({ key: `[0]` })
+ ])
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
.alternate as ConditionalExpression
expect((branch2.consequent as CallExpression).arguments).toMatchObject([
`"p"`,
- `{ key: 1 }`
+ createObjectMatcher({ key: `[1]` })
])
expect((branch2.alternate as CallExpression).arguments).toMatchObject([
`_${FRAGMENT}`,
- `{ key: 2 }`,
+ createObjectMatcher({ key: `[2]` }),
[
{
type: NodeTypes.TEXT,
@@ -437,7 +449,7 @@ describe('compiler: v-if', () => {
expect(branch1.arguments[1]).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${MERGE_PROPS}`,
- arguments: [`{ key: 0 }`, { content: `obj` }]
+ arguments: [createObjectMatcher({ key: `[0]` }), { content: `obj` }]
})
})
@@ -470,7 +482,7 @@ describe('compiler: v-if', () => {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${MERGE_PROPS}`,
arguments: [
- `{ key: 0 }`,
+ createObjectMatcher({ key: `[0]` }),
{ content: `obj` },
createObjectMatcher({
id: 'foo'
@@ -487,9 +499,11 @@ describe('compiler: v-if', () => {
.consequent as CallExpression
expect(branch1.callee).toBe(`_${APPLY_DIRECTIVES}`)
const realBranch = branch1.arguments[0] as CallExpression
- expect(realBranch.arguments[1]).toBe(`{ key: 0 }`)
+ expect(realBranch.arguments[1]).toMatchObject(
+ createObjectMatcher({ key: `[0]` })
+ )
})
- test('with comments', () => {})
+ test.todo('with comments')
})
})
diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts
index e1d23625..6d1bb90e 100644
--- a/packages/compiler-core/src/transform.ts
+++ b/packages/compiler-core/src/transform.ts
@@ -15,7 +15,7 @@ import {
import { isString, isArray } from '@vue/shared'
import { CompilerError, defaultOnError } from './errors'
import { TO_STRING, COMMENT, CREATE_VNODE, FRAGMENT } from './runtimeConstants'
-import { isVSlot, createBlockExpression } from './utils'
+import { isVSlot, createBlockExpression, isSlotOutlet } from './utils'
// There are two types of transforms:
//
@@ -192,22 +192,20 @@ function finalizeRoot(root: RootNode, context: TransformContext) {
const { children } = root
if (children.length === 1) {
const child = children[0]
- if (child.type === NodeTypes.ELEMENT && child.codegenNode) {
- // only child is a - it needs to be in a fragment block.
- if (child.tagType === ElementTypes.SLOT) {
- root.codegenNode = createBlockExpression(
- [helper(FRAGMENT), `null`, child.codegenNode!],
- context
- )
- } else {
- // turn root element into a block
- root.codegenNode = createBlockExpression(
- child.codegenNode!.arguments,
- context
- )
- }
+ if (
+ child.type === NodeTypes.ELEMENT &&
+ !isSlotOutlet(child) &&
+ child.codegenNode
+ ) {
+ // turn root element into a block
+ root.codegenNode = createBlockExpression(
+ child.codegenNode!.arguments,
+ context
+ )
} else {
- // IfNode, ForNode, TextNodes or transform calls without transformElement.
+ // - single , IfNode, ForNode: already blocks.
+ // - single text node: always patched.
+ // - transform calls without transformElement (only during tests)
// Just generate the node as-is
root.codegenNode = child
}
diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts
index e4f37a22..069c7934 100644
--- a/packages/compiler-core/src/transforms/transformElement.ts
+++ b/packages/compiler-core/src/transforms/transformElement.ts
@@ -101,15 +101,9 @@ export const transformElement: NodeTransform = (node, context) => {
if (hasDynamicTextChild) {
patchFlag |= PatchFlags.TEXT
}
- // pass directly if the only child is one of:
- // - text (plain / interpolation / expression)
- // - outlet (already an array)
- if (
- type === NodeTypes.TEXT ||
- hasDynamicTextChild ||
- (type === NodeTypes.ELEMENT &&
- (child as ElementNode).tagType === ElementTypes.SLOT)
- ) {
+ // pass directly if the only child is a text node
+ // (plain / interpolation / expression)
+ if (hasDynamicTextChild || type === NodeTypes.TEXT) {
args.push(child)
} else {
args.push(node.children)
@@ -171,7 +165,7 @@ export const transformElement: NodeTransform = (node, context) => {
}
}
-type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
+export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
export function buildProps(
props: ElementNode['props'],
diff --git a/packages/compiler-core/src/transforms/transfromSlotOutlet.ts b/packages/compiler-core/src/transforms/transfromSlotOutlet.ts
index 24032550..3ed95a4e 100644
--- a/packages/compiler-core/src/transforms/transfromSlotOutlet.ts
+++ b/packages/compiler-core/src/transforms/transfromSlotOutlet.ts
@@ -1,19 +1,18 @@
import { NodeTransform } from '../transform'
import {
NodeTypes,
- ElementTypes,
CompoundExpressionNode,
createCompoundExpression,
CallExpression,
createCallExpression
} from '../ast'
-import { isSimpleIdentifier } from '../utils'
+import { isSimpleIdentifier, isSlotOutlet } from '../utils'
import { buildProps } from './transformElement'
import { createCompilerError, ErrorCodes } from '../errors'
import { RENDER_SLOT } from '../runtimeConstants'
export const transformSlotOutlet: NodeTransform = (node, context) => {
- if (node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT) {
+ if (isSlotOutlet(node)) {
const { props, children, loc } = node
const $slots = context.prefixIdentifiers ? `_ctx.$slots` : `$slots`
let slot: string | CompoundExpressionNode = $slots + `.default`
diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts
index fbcbdfa5..6ae47d3e 100644
--- a/packages/compiler-core/src/transforms/vFor.ts
+++ b/packages/compiler-core/src/transforms/vFor.ts
@@ -12,14 +12,18 @@ import {
createCallExpression,
createFunctionExpression,
ElementTypes,
- ObjectExpression,
createObjectExpression,
- createObjectProperty,
- TemplateChildNode,
- CallExpression
+ createObjectProperty
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
-import { getInnerRange, findProp, createBlockExpression } from '../utils'
+import {
+ getInnerRange,
+ findProp,
+ createBlockExpression,
+ isTemplateNode,
+ isSlotOutlet,
+ injectProp
+} from '../utils'
import {
RENDER_LIST,
OPEN_BLOCK,
@@ -28,6 +32,7 @@ import {
} from '../runtimeConstants'
import { processExpression } from './transformExpression'
import { PatchFlags, PatchFlagNames } from '@vue/shared'
+import { PropsExpression } from './transformElement'
export const transformFor = createStructuralDirectiveTransform(
'for',
@@ -91,40 +96,52 @@ export const transformFor = createStructuralDirectiveTransform(
// finish the codegen now that all children have been traversed
let childBlock
- if (node.tagType === ElementTypes.TEMPLATE) {
+ const isTemplate = isTemplateNode(node)
+ const slotOutlet = isSlotOutlet(node)
+ ? node
+ : isTemplate &&
+ node.children.length === 1 &&
+ isSlotOutlet(node.children[0])
+ ? node.children[0]
+ : null
+ const keyProperty = keyProp
+ ? createObjectProperty(
+ `key`,
+ keyProp.type === NodeTypes.ATTRIBUTE
+ ? createSimpleExpression(keyProp.value!.content, true)
+ : keyProp.exp!
+ )
+ : null
+ if (slotOutlet) {
+ // or
+ childBlock = slotOutlet.codegenNode!
+ if (isTemplate && keyProperty) {
+ //
+ // we need to inject the key to the renderSlot() call.
+ const existingProps = childBlock.arguments[1] as
+ | PropsExpression
+ | undefined
+ | 'null'
+ childBlock.arguments[1] = injectProp(
+ existingProps,
+ keyProperty,
+ context
+ )
+ }
+ } else if (isTemplate) {
//
// should genereate a fragment block for each loop
- let childBlockProps: string | ObjectExpression = `null`
- if (keyProp) {
- childBlockProps = createObjectExpression([
- createObjectProperty(
- `key`,
- keyProp.type === NodeTypes.ATTRIBUTE
- ? createSimpleExpression(keyProp.value!.content, true)
- : keyProp.exp!
- )
- ])
- }
- let childBlockChildren: TemplateChildNode[] | CallExpression =
- node.children
- // if the only child is a , use it directly as fragment
- // children since it already returns an array.
- if (childBlockChildren.length === 1) {
- const child = childBlockChildren[0]
- if (
- child.type === NodeTypes.ELEMENT &&
- child.tagType === ElementTypes.SLOT
- ) {
- childBlockChildren = child.codegenNode!
- }
- }
childBlock = createBlockExpression(
- [helper(FRAGMENT), childBlockProps, childBlockChildren],
+ [
+ helper(FRAGMENT),
+ keyProperty ? createObjectExpression([keyProperty]) : `null`,
+ node.children
+ ],
context
)
} else {
- // Normal element v-for. Directly use the child's codegenNode arguments,
- // but replace createVNode() with createBlock()
+ // Normal element v-for. Directly use the child's codegenNode
+ // arguments, but replace createVNode() with createBlock()
childBlock = createBlockExpression(
node.codegenNode!.arguments,
context
diff --git a/packages/compiler-core/src/transforms/vIf.ts b/packages/compiler-core/src/transforms/vIf.ts
index 167ca7d3..9dd27e1f 100644
--- a/packages/compiler-core/src/transforms/vIf.ts
+++ b/packages/compiler-core/src/transforms/vIf.ts
@@ -16,11 +16,8 @@ import {
ConditionalExpression,
CallExpression,
createSimpleExpression,
- JSChildNode,
- ObjectExpression,
createObjectProperty,
- Property,
- ExpressionNode
+ createObjectExpression
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
@@ -30,9 +27,10 @@ import {
EMPTY,
FRAGMENT,
APPLY_DIRECTIVES,
- MERGE_PROPS
+ CREATE_VNODE
} from '../runtimeConstants'
-import { isString } from '@vue/shared'
+import { injectProp } from '../utils'
+import { PropsExpression } from './transformElement'
export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/,
@@ -153,81 +151,52 @@ function createCodegenNodeForBranch(
function createChildrenCodegenNode(
branch: IfBranchNode,
index: number,
- { helper }: TransformContext
+ context: TransformContext
): CallExpression {
- const keyExp = `{ key: ${index} }`
+ const { helper } = context
+ const keyProperty = createObjectProperty(
+ `key`,
+ createSimpleExpression(index + '', false)
+ )
const { children } = branch
const child = children[0]
const needFragmentWrapper =
- children.length !== 1 ||
- child.type !== NodeTypes.ELEMENT ||
- child.tagType === ElementTypes.SLOT
+ children.length !== 1 || child.type !== NodeTypes.ELEMENT
if (needFragmentWrapper) {
const blockArgs: CallExpression['arguments'] = [
helper(FRAGMENT),
- keyExp,
+ createObjectExpression([keyProperty]),
children
]
- if (children.length === 1) {
+ if (children.length === 1 && child.type === NodeTypes.FOR) {
// optimize away nested fragments when child is a ForNode
- if (child.type === NodeTypes.FOR) {
- const forBlockArgs = (child.codegenNode
- .expressions[1] as CallExpression).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
- ) {
- //
- // since slot always returns array, use it directly as the fragment children.
- blockArgs[2] = child.codegenNode!
- }
+ const forBlockArgs = (child.codegenNode.expressions[1] as CallExpression)
+ .arguments
+ // directly use the for block's children and patchFlag
+ blockArgs[2] = forBlockArgs[2]
+ blockArgs[3] = forBlockArgs[3]
}
return createCallExpression(helper(CREATE_BLOCK), blockArgs)
} else {
const childCodegen = (child as ElementNode).codegenNode!
let vnodeCall = childCodegen
+ // Element with custom directives. Locate the actual createVNode() call.
if (vnodeCall.callee.includes(APPLY_DIRECTIVES)) {
vnodeCall = vnodeCall.arguments[0] as CallExpression
}
- // change child to a block
- vnodeCall.callee = helper(CREATE_BLOCK)
- // branch key
- const existingProps = vnodeCall.arguments[1]
- if (!existingProps || existingProps === `null`) {
- vnodeCall.arguments[1] = keyExp
- } else {
- // inject branch key if not already have a key
- const props = existingProps as
- | CallExpression
- | ObjectExpression
- | ExpressionNode
- if (props.type === NodeTypes.JS_CALL_EXPRESSION) {
- // merged props... add ours
- // only inject key to object literal if it's the first argument so that
- // if doesn't override user provided keys
- const first = props.arguments[0] as string | JSChildNode
- if (!isString(first) && first.type === NodeTypes.JS_OBJECT_EXPRESSION) {
- first.properties.unshift(createKeyProperty(index))
- } else {
- props.arguments.unshift(keyExp)
- }
- } else if (props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
- props.properties.unshift(createKeyProperty(index))
- } else {
- // single v-bind with expression
- vnodeCall.arguments[1] = createCallExpression(helper(MERGE_PROPS), [
- keyExp,
- props
- ])
- }
+ // Change createVNode to createBlock.
+ // It's possible to have renderSlot() here as well - which already produces
+ // a block, so no need to change the callee. renderSlot() also accepts props
+ // as the 2nd argument, so the key injection logic below works for it too.
+ if (vnodeCall.callee.includes(CREATE_VNODE)) {
+ vnodeCall.callee = helper(CREATE_BLOCK)
}
+ // inject branch key
+ const existingProps = vnodeCall.arguments[1] as
+ | PropsExpression
+ | undefined
+ | 'null'
+ vnodeCall.arguments[1] = injectProp(existingProps, keyProperty, context)
return childCodegen
}
}
-
-function createKeyProperty(index: number): Property {
- return createObjectProperty(`key`, createSimpleExpression(index + '', false))
-}
diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts
index 37cac6cc..ab19a7d0 100644
--- a/packages/compiler-core/src/utils.ts
+++ b/packages/compiler-core/src/utils.ts
@@ -10,13 +10,18 @@ import {
DirectiveNode,
ElementTypes,
TemplateChildNode,
- RootNode
+ RootNode,
+ ObjectExpression,
+ Property,
+ JSChildNode,
+ createObjectExpression
} from './ast'
import { parse } from 'acorn'
import { walk } from 'estree-walker'
import { TransformContext } from './transform'
-import { OPEN_BLOCK, CREATE_BLOCK } from './runtimeConstants'
+import { OPEN_BLOCK, CREATE_BLOCK, MERGE_PROPS } from './runtimeConstants'
import { isString } from '@vue/shared'
+import { PropsExpression } from './transforms/transformElement'
// cache node requires
// lazy require dependencies so that they don't end up in rollup's dep graph
@@ -165,5 +170,40 @@ export const isVSlot = (p: ElementNode['props'][0]): p is DirectiveNode =>
export const isTemplateNode = (
node: RootNode | TemplateChildNode
-): node is ElementNode =>
+): node is ElementNode & { tagType: ElementTypes.TEMPLATE } =>
node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.TEMPLATE
+
+export const isSlotOutlet = (
+ node: RootNode | TemplateChildNode
+): node is ElementNode & { tagType: ElementTypes.SLOT } =>
+ node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT
+
+export function injectProp(
+ props: PropsExpression | undefined | 'null',
+ prop: Property,
+ context: TransformContext
+): ObjectExpression | CallExpression {
+ if (props == null || props === `null`) {
+ return createObjectExpression([prop])
+ } else if (props.type === NodeTypes.JS_CALL_EXPRESSION) {
+ // merged props... add ours
+ // only inject key to object literal if it's the first argument so that
+ // if doesn't override user provided keys
+ const first = props.arguments[0] as string | JSChildNode
+ if (!isString(first) && first.type === NodeTypes.JS_OBJECT_EXPRESSION) {
+ first.properties.unshift(prop)
+ } else {
+ props.arguments.unshift(createObjectExpression([prop]))
+ }
+ return props
+ } else if (props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
+ props.properties.unshift(prop)
+ return props
+ } else {
+ // single v-bind with expression, return a merged replacement
+ return createCallExpression(context.helper(MERGE_PROPS), [
+ createObjectExpression([prop]),
+ props
+ ])
+ }
+}
diff --git a/packages/runtime-core/src/helpers/renderSlot.ts b/packages/runtime-core/src/helpers/renderSlot.ts
index 3cb2c25e..5e14df25 100644
--- a/packages/runtime-core/src/helpers/renderSlot.ts
+++ b/packages/runtime-core/src/helpers/renderSlot.ts
@@ -1,12 +1,25 @@
import { Slot } from '../componentSlots'
-import { VNodeChildren } from '../vnode'
+import {
+ VNodeChildren,
+ openBlock,
+ createBlock,
+ Fragment,
+ VNode
+} from '../vnode'
export function renderSlot(
slot: Slot | undefined,
props: any = {},
// this is not a user-facing function, so the fallback is always generated by
- // the compiler.
- fallback?: string | VNodeChildren
-): string | VNodeChildren | null {
- return slot ? slot() : fallback || null
+ // the compiler and gurunteed to be an array
+ fallback?: VNodeChildren
+): VNode {
+ return (
+ openBlock(),
+ createBlock(
+ Fragment,
+ { key: props.key },
+ slot ? slot(props) : fallback || []
+ )
+ )
}