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