wip(compiler): adjust renderSlot() signature

This commit is contained in:
Evan You 2019-10-03 14:29:12 -04:00
parent 306c22efe1
commit 05db2a9c6c
9 changed files with 83 additions and 67 deletions

View File

@ -277,8 +277,7 @@ describe('compiler: transform', () => {
expect(ast.codegenNode).toMatchObject({ expect(ast.codegenNode).toMatchObject({
codegenNode: { codegenNode: {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`, callee: `_${RENDER_SLOT}`
arguments: ['$slots.default']
} }
}) })
}) })

View File

@ -112,7 +112,7 @@ 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 _renderSlot($slots.default) return _renderSlot($slots, \\"default\\")
}), 128 /* UNKEYED_FRAGMENT */)) }), 128 /* UNKEYED_FRAGMENT */))
} }
}" }"
@ -126,7 +126,7 @@ 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 _renderSlot($slots.default) return _renderSlot($slots, \\"default\\")
}), 128 /* UNKEYED_FRAGMENT */)) }), 128 /* UNKEYED_FRAGMENT */))
} }
}" }"

View File

@ -40,7 +40,7 @@ return function render() {
const { openBlock: _openBlock, renderSlot: _renderSlot, createBlock: _createBlock, Empty: _Empty } = _Vue const { openBlock: _openBlock, renderSlot: _renderSlot, createBlock: _createBlock, Empty: _Empty } = _Vue
return (_openBlock(), ok return (_openBlock(), ok
? _renderSlot($slots.default, { key: 0 }) ? _renderSlot($slots, \\"default\\", { key: 0 })
: _createBlock(_Empty)) : _createBlock(_Empty))
} }
}" }"
@ -91,3 +91,17 @@ return function render() {
} }
}" }"
`; `;
exports[`compiler: v-if codegen v-if on <slot/> 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { openBlock: _openBlock, renderSlot: _renderSlot, createBlock: _createBlock, Empty: _Empty } = _Vue
return (_openBlock(), ok
? _renderSlot($slots, \\"default\\", { key: 0 })
: _createBlock(_Empty))
}
}"
`;

View File

@ -36,7 +36,7 @@ describe('compiler: transform <slot> outlets', () => {
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({ expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`, callee: `_${RENDER_SLOT}`,
arguments: [`$slots.default`] arguments: [`$slots`, `"default"`]
}) })
}) })
@ -45,16 +45,7 @@ describe('compiler: transform <slot> outlets', () => {
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({ expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`, callee: `_${RENDER_SLOT}`,
arguments: [`$slots.foo`] arguments: [`$slots`, `"foo"`]
})
})
test('statically named slot outlet w/ name that needs quotes', () => {
const ast = parseWithSlots(`<slot name="foo-bar" />`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [`$slots["foo-bar"]`]
}) })
}) })
@ -64,17 +55,11 @@ describe('compiler: transform <slot> outlets', () => {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`, callee: `_${RENDER_SLOT}`,
arguments: [ arguments: [
{ `$slots`,
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`$slots[`,
{ {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `foo`, content: `foo`,
isStatic: false isStatic: false
},
`]`
]
} }
] ]
}) })
@ -88,10 +73,10 @@ describe('compiler: transform <slot> outlets', () => {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT, callee: RENDER_SLOT,
arguments: [ arguments: [
`_ctx.$slots`,
{ {
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [ children: [
`_ctx.$slots[`,
{ {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.foo`, content: `_ctx.foo`,
@ -102,8 +87,7 @@ describe('compiler: transform <slot> outlets', () => {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.bar`, content: `_ctx.bar`,
isStatic: false isStatic: false
}, }
`]`
] ]
} }
] ]
@ -116,7 +100,8 @@ describe('compiler: transform <slot> outlets', () => {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`, callee: `_${RENDER_SLOT}`,
arguments: [ arguments: [
`$slots.default`, `$slots`,
`"default"`,
{ {
type: NodeTypes.JS_OBJECT_EXPRESSION, type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [ properties: [
@ -152,7 +137,8 @@ describe('compiler: transform <slot> outlets', () => {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`, callee: `_${RENDER_SLOT}`,
arguments: [ arguments: [
`$slots.foo`, `$slots`,
`"foo"`,
{ {
type: NodeTypes.JS_OBJECT_EXPRESSION, type: NodeTypes.JS_OBJECT_EXPRESSION,
// props should not include name // props should not include name
@ -189,10 +175,8 @@ describe('compiler: transform <slot> outlets', () => {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`, callee: `_${RENDER_SLOT}`,
arguments: [ arguments: [
{ `$slots`,
type: NodeTypes.COMPOUND_EXPRESSION, { content: `foo`, isStatic: false },
children: [`$slots[`, { content: `foo` }, `]`]
},
{ {
type: NodeTypes.JS_OBJECT_EXPRESSION, type: NodeTypes.JS_OBJECT_EXPRESSION,
// props should not include name // props should not include name
@ -229,7 +213,8 @@ describe('compiler: transform <slot> outlets', () => {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`, callee: `_${RENDER_SLOT}`,
arguments: [ arguments: [
`$slots.default`, `$slots`,
`"default"`,
`{}`, `{}`,
[ [
{ {
@ -247,7 +232,8 @@ describe('compiler: transform <slot> outlets', () => {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`, callee: `_${RENDER_SLOT}`,
arguments: [ arguments: [
`$slots.foo`, `$slots`,
`"foo"`,
`{}`, `{}`,
[ [
{ {
@ -265,7 +251,8 @@ describe('compiler: transform <slot> outlets', () => {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`, callee: `_${RENDER_SLOT}`,
arguments: [ arguments: [
`$slots.default`, `$slots`,
`"default"`,
{ {
type: NodeTypes.JS_OBJECT_EXPRESSION, type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [ properties: [
@ -297,7 +284,8 @@ describe('compiler: transform <slot> outlets', () => {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`, callee: `_${RENDER_SLOT}`,
arguments: [ arguments: [
`$slots.foo`, `$slots`,
`"foo"`,
{ {
type: NodeTypes.JS_OBJECT_EXPRESSION, type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [ properties: [

View File

@ -360,7 +360,23 @@ describe('compiler: v-if', () => {
expect(branch1).toMatchObject({ expect(branch1).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`, callee: `_${RENDER_SLOT}`,
arguments: ['$slots.default', createObjectMatcher({ key: `[0]` })] arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })]
})
expect(generate(root).code).toMatchSnapshot()
})
test('v-if on <slot/>', () => {
const {
root,
node: { codegenNode }
} = parseWithIfTransform(`<slot v-if="ok"></slot>`)
// assertSharedCodegen(codegenNode)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })]
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })

View File

@ -1,12 +1,11 @@
import { NodeTransform } from '../transform' import { NodeTransform } from '../transform'
import { import {
NodeTypes, NodeTypes,
CompoundExpressionNode,
createCompoundExpression,
CallExpression, CallExpression,
createCallExpression createCallExpression,
ExpressionNode
} from '../ast' } from '../ast'
import { isSimpleIdentifier, isSlotOutlet } from '../utils' import { 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'
@ -15,7 +14,7 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
if (isSlotOutlet(node)) { 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 slotName: string | ExpressionNode = `"default"`
// check for <slot name="xxx" OR :name="xxx" /> // check for <slot name="xxx" OR :name="xxx" />
let nameIndex: number = -1 let nameIndex: number = -1
@ -24,11 +23,7 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
if (prop.type === NodeTypes.ATTRIBUTE) { if (prop.type === NodeTypes.ATTRIBUTE) {
if (prop.name === `name` && prop.value) { if (prop.name === `name` && prop.value) {
// static name="xxx" // static name="xxx"
const name = prop.value.content slotName = JSON.stringify(prop.value.content)
const accessor = isSimpleIdentifier(name)
? `.${name}`
: `[${JSON.stringify(name)}]`
slot = `${$slots}${accessor}`
nameIndex = i nameIndex = i
break break
} }
@ -42,20 +37,14 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
arg.content === `name` arg.content === `name`
) { ) {
// dynamic :name="xxx" // dynamic :name="xxx"
slot = createCompoundExpression([ slotName = exp
$slots + `[`,
...(exp.type === NodeTypes.SIMPLE_EXPRESSION
? [exp]
: exp.children),
`]`
])
nameIndex = i nameIndex = i
break break
} }
} }
} }
const slotArgs: CallExpression['arguments'] = [slot] const slotArgs: CallExpression['arguments'] = [$slots, slotName]
const propsWithoutName = const propsWithoutName =
nameIndex > -1 nameIndex > -1
? props.slice(0, nameIndex).concat(props.slice(nameIndex + 1)) ? props.slice(0, nameIndex).concat(props.slice(nameIndex + 1))

View File

@ -118,11 +118,12 @@ export const transformFor = createStructuralDirectiveTransform(
if (isTemplate && keyProperty) { if (isTemplate && keyProperty) {
// <template v-for="..." :key="..."><slot/></template> // <template v-for="..." :key="..."><slot/></template>
// we need to inject the key to the renderSlot() call. // we need to inject the key to the renderSlot() call.
const existingProps = childBlock.arguments[1] as // the props for renderSlot is passed as the 3rd argument.
const existingProps = childBlock.arguments[2] as
| PropsExpression | PropsExpression
| undefined | undefined
| 'null' | 'null'
childBlock.arguments[1] = injectProp( childBlock.arguments[2] = injectProp(
existingProps, existingProps,
keyProperty, keyProperty,
context context

View File

@ -27,7 +27,8 @@ import {
EMPTY, EMPTY,
FRAGMENT, FRAGMENT,
APPLY_DIRECTIVES, APPLY_DIRECTIVES,
CREATE_VNODE CREATE_VNODE,
RENDER_SLOT
} from '../runtimeConstants' } from '../runtimeConstants'
import { injectProp } from '../utils' import { injectProp } from '../utils'
import { PropsExpression } from './transformElement' import { PropsExpression } from './transformElement'
@ -185,18 +186,24 @@ function createChildrenCodegenNode(
vnodeCall = vnodeCall.arguments[0] as CallExpression vnodeCall = vnodeCall.arguments[0] as CallExpression
} }
// Change createVNode to createBlock. // 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)) { if (vnodeCall.callee.includes(CREATE_VNODE)) {
vnodeCall.callee = helper(CREATE_BLOCK) vnodeCall.callee = helper(CREATE_BLOCK)
} }
// It's possible to have renderSlot() here as well - which already produces
// a block, so no need to change the callee. However it accepts props at
// a different arg index so make sure to check for so that the key injection
// logic below works for it too.
const propsIndex = vnodeCall.callee.includes(RENDER_SLOT) ? 2 : 1
// inject branch key // inject branch key
const existingProps = vnodeCall.arguments[1] as const existingProps = vnodeCall.arguments[propsIndex] as
| PropsExpression | PropsExpression
| undefined | undefined
| 'null' | 'null'
vnodeCall.arguments[1] = injectProp(existingProps, keyProperty, context) vnodeCall.arguments[propsIndex] = injectProp(
existingProps,
keyProperty,
context
)
return childCodegen return childCodegen
} }
} }

View File

@ -8,12 +8,14 @@ import {
} from '../vnode' } from '../vnode'
export function renderSlot( export function renderSlot(
slot: Slot | undefined, slots: Record<string, Slot>,
key: string,
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 and gurunteed to be an array // the compiler and gurunteed to be an array
fallback?: VNodeChildren fallback?: VNodeChildren
): VNode { ): VNode {
const slot = slots[key]
return ( return (
openBlock(), openBlock(),
createBlock( createBlock(