feat(compiler): transform slot outlets
This commit is contained in:
parent
d900c13efb
commit
ee66ce78b7
323
packages/compiler-core/__tests__/transforms/vSlot.spec.ts
Normal file
323
packages/compiler-core/__tests__/transforms/vSlot.spec.ts
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
import {
|
||||||
|
CompilerOptions,
|
||||||
|
parse,
|
||||||
|
transform,
|
||||||
|
ElementNode,
|
||||||
|
NodeTypes
|
||||||
|
} from '../../src'
|
||||||
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
|
import { transformOn } from '../../src/transforms/vOn'
|
||||||
|
import { transformBind } from '../../src/transforms/vBind'
|
||||||
|
import { transformExpression } from '../../src/transforms/transformExpression'
|
||||||
|
import { RENDER_SLOT } from '../../src/runtimeConstants'
|
||||||
|
|
||||||
|
function parseWithSlots(template: string, options: CompilerOptions = {}) {
|
||||||
|
const ast = parse(template)
|
||||||
|
transform(ast, {
|
||||||
|
nodeTransforms: [
|
||||||
|
...(options.prefixIdentifiers ? [transformExpression] : []),
|
||||||
|
// slot transform is part of transformElement
|
||||||
|
transformElement
|
||||||
|
],
|
||||||
|
directiveTransforms: {
|
||||||
|
on: transformOn,
|
||||||
|
bind: transformBind
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
return ast
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('compiler: transform slots', () => {
|
||||||
|
test('default slot outlet', () => {
|
||||||
|
const ast = parseWithSlots(`<slot/>`)
|
||||||
|
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: `_${RENDER_SLOT}`,
|
||||||
|
arguments: [`$slots.default`]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('statically named slot outlet', () => {
|
||||||
|
const ast = parseWithSlots(`<slot name="foo" />`)
|
||||||
|
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: `_${RENDER_SLOT}`,
|
||||||
|
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"]`]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('dynamically named slot outlet', () => {
|
||||||
|
const ast = parseWithSlots(`<slot :name="foo" />`)
|
||||||
|
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: `_${RENDER_SLOT}`,
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [
|
||||||
|
`$slots[`,
|
||||||
|
{
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `foo`,
|
||||||
|
isStatic: false
|
||||||
|
},
|
||||||
|
`]`
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('dynamically named slot outlet w/ prefixIdentifiers: true', () => {
|
||||||
|
const ast = parseWithSlots(`<slot :name="foo + bar" />`, {
|
||||||
|
prefixIdentifiers: true
|
||||||
|
})
|
||||||
|
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: RENDER_SLOT,
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [
|
||||||
|
`_ctx.$slots[`,
|
||||||
|
{
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `_ctx.foo`,
|
||||||
|
isStatic: false
|
||||||
|
},
|
||||||
|
` + `,
|
||||||
|
{
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `_ctx.bar`,
|
||||||
|
isStatic: false
|
||||||
|
},
|
||||||
|
`]`
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('default slot outlet with props', () => {
|
||||||
|
const ast = parseWithSlots(`<slot foo="bar" :baz="qux" />`)
|
||||||
|
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: `_${RENDER_SLOT}`,
|
||||||
|
arguments: [
|
||||||
|
`$slots.default`,
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
key: {
|
||||||
|
content: `foo`,
|
||||||
|
isStatic: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
content: `bar`,
|
||||||
|
isStatic: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: {
|
||||||
|
content: `baz`,
|
||||||
|
isStatic: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
content: `qux`,
|
||||||
|
isStatic: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('statically named slot outlet with props', () => {
|
||||||
|
const ast = parseWithSlots(`<slot name="foo" foo="bar" :baz="qux" />`)
|
||||||
|
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: `_${RENDER_SLOT}`,
|
||||||
|
arguments: [
|
||||||
|
`$slots.foo`,
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
||||||
|
// props should not include name
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
key: {
|
||||||
|
content: `foo`,
|
||||||
|
isStatic: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
content: `bar`,
|
||||||
|
isStatic: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: {
|
||||||
|
content: `baz`,
|
||||||
|
isStatic: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
content: `qux`,
|
||||||
|
isStatic: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('dynamically named slot outlet with props', () => {
|
||||||
|
const ast = parseWithSlots(`<slot :name="foo" foo="bar" :baz="qux" />`)
|
||||||
|
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: `_${RENDER_SLOT}`,
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [`$slots[`, { content: `foo` }, `]`]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
||||||
|
// props should not include name
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
key: {
|
||||||
|
content: `foo`,
|
||||||
|
isStatic: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
content: `bar`,
|
||||||
|
isStatic: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: {
|
||||||
|
content: `baz`,
|
||||||
|
isStatic: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
content: `qux`,
|
||||||
|
isStatic: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('default slot outlet with fallback', () => {
|
||||||
|
const ast = parseWithSlots(`<slot><div/></slot>`)
|
||||||
|
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: `_${RENDER_SLOT}`,
|
||||||
|
arguments: [
|
||||||
|
`$slots.default`,
|
||||||
|
`{}`,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
tag: `div`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('named slot outlet with fallback', () => {
|
||||||
|
const ast = parseWithSlots(`<slot name="foo"><div/></slot>`)
|
||||||
|
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: `_${RENDER_SLOT}`,
|
||||||
|
arguments: [
|
||||||
|
`$slots.foo`,
|
||||||
|
`{}`,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
tag: `div`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('default slot outlet with props & fallback', () => {
|
||||||
|
const ast = parseWithSlots(`<slot :foo="bar"><div/></slot>`)
|
||||||
|
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: `_${RENDER_SLOT}`,
|
||||||
|
arguments: [
|
||||||
|
`$slots.default`,
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
key: {
|
||||||
|
content: `foo`,
|
||||||
|
isStatic: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
content: `bar`,
|
||||||
|
isStatic: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
tag: `div`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('named slot outlet with props & fallback', () => {
|
||||||
|
const ast = parseWithSlots(`<slot name="foo" :foo="bar"><div/></slot>`)
|
||||||
|
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: `_${RENDER_SLOT}`,
|
||||||
|
arguments: [
|
||||||
|
`$slots.foo`,
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
key: {
|
||||||
|
content: `foo`,
|
||||||
|
isStatic: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
content: `bar`,
|
||||||
|
isStatic: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
tag: `div`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -160,8 +160,8 @@ export type JSChildNode =
|
|||||||
|
|
||||||
export interface CallExpression extends Node {
|
export interface CallExpression extends Node {
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION
|
type: NodeTypes.JS_CALL_EXPRESSION
|
||||||
callee: string // can only be imported runtime helpers, so no source location
|
callee: string | ExpressionNode
|
||||||
arguments: Array<string | JSChildNode | ChildNode[]>
|
arguments: (string | JSChildNode | ChildNode[])[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ObjectExpression extends Node {
|
export interface ObjectExpression extends Node {
|
||||||
@ -253,7 +253,7 @@ export function createCompoundExpression(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createCallExpression(
|
export function createCallExpression(
|
||||||
callee: string,
|
callee: string | ExpressionNode,
|
||||||
args: CallExpression['arguments'],
|
args: CallExpression['arguments'],
|
||||||
loc: SourceLocation
|
loc: SourceLocation
|
||||||
): CallExpression {
|
): CallExpression {
|
||||||
|
@ -17,7 +17,8 @@ import {
|
|||||||
Position,
|
Position,
|
||||||
InterpolationNode,
|
InterpolationNode,
|
||||||
CompoundExpressionNode,
|
CompoundExpressionNode,
|
||||||
SimpleExpressionNode
|
SimpleExpressionNode,
|
||||||
|
ElementTypes
|
||||||
} from './ast'
|
} from './ast'
|
||||||
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
||||||
import {
|
import {
|
||||||
@ -262,7 +263,10 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
|
|||||||
|
|
||||||
// This will generate a single vnode call if:
|
// This will generate a single vnode call if:
|
||||||
// - The target position explicitly allows a single node (root, if, for)
|
// - The target position explicitly allows a single node (root, if, for)
|
||||||
// - The list has length === 1, AND The only child is a text, expression or comment.
|
// - The list has length === 1, AND The only child is a:
|
||||||
|
// - text
|
||||||
|
// - expression
|
||||||
|
// - <slot> outlet, which always produces an array
|
||||||
function genChildren(
|
function genChildren(
|
||||||
children: ChildNode[],
|
children: ChildNode[],
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
@ -272,12 +276,14 @@ function genChildren(
|
|||||||
return context.push(`null`)
|
return context.push(`null`)
|
||||||
}
|
}
|
||||||
const child = children[0]
|
const child = children[0]
|
||||||
|
const type = child.type
|
||||||
if (
|
if (
|
||||||
children.length === 1 &&
|
children.length === 1 &&
|
||||||
(allowSingle ||
|
(allowSingle ||
|
||||||
child.type === NodeTypes.TEXT ||
|
type === NodeTypes.TEXT ||
|
||||||
child.type === NodeTypes.INTERPOLATION ||
|
type === NodeTypes.INTERPOLATION ||
|
||||||
child.type === NodeTypes.COMMENT)
|
(type === NodeTypes.ELEMENT &&
|
||||||
|
(child as ElementNode).tagType === ElementTypes.SLOT))
|
||||||
) {
|
) {
|
||||||
genNode(child, context)
|
genNode(child, context)
|
||||||
} else {
|
} else {
|
||||||
@ -523,7 +529,12 @@ function genCallExpression(
|
|||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
multilines = node.arguments.length > 2
|
multilines = node.arguments.length > 2
|
||||||
) {
|
) {
|
||||||
|
if (isString(node.callee)) {
|
||||||
context.push(node.callee + `(`, node, true)
|
context.push(node.callee + `(`, node, true)
|
||||||
|
} else {
|
||||||
|
genNode(node.callee, context)
|
||||||
|
context.push(`(`)
|
||||||
|
}
|
||||||
multilines && context.indent()
|
multilines && context.indent()
|
||||||
genNodeList(node.arguments, context, multilines)
|
genNodeList(node.arguments, context, multilines)
|
||||||
multilines && context.deindent()
|
multilines && context.deindent()
|
||||||
|
@ -68,6 +68,7 @@ export const enum ErrorCodes {
|
|||||||
X_FOR_MALFORMED_EXPRESSION,
|
X_FOR_MALFORMED_EXPRESSION,
|
||||||
X_V_BIND_NO_EXPRESSION,
|
X_V_BIND_NO_EXPRESSION,
|
||||||
X_V_ON_NO_EXPRESSION,
|
X_V_ON_NO_EXPRESSION,
|
||||||
|
X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
|
||||||
|
|
||||||
// generic errors
|
// generic errors
|
||||||
X_PREFIX_ID_NOT_SUPPORTED,
|
X_PREFIX_ID_NOT_SUPPORTED,
|
||||||
@ -138,6 +139,7 @@ export const errorMessages: { [code: number]: string } = {
|
|||||||
[ErrorCodes.X_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression`,
|
[ErrorCodes.X_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression`,
|
||||||
[ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression`,
|
[ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression`,
|
||||||
[ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression`,
|
[ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression`,
|
||||||
|
[ErrorCodes.X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `unexpected custom directive on <slot> outlet`,
|
||||||
|
|
||||||
// generic errors
|
// generic errors
|
||||||
[ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
|
[ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
|
||||||
|
@ -10,6 +10,7 @@ export const RESOLVE_COMPONENT = `resolveComponent`
|
|||||||
export const RESOLVE_DIRECTIVE = `resolveDirective`
|
export const RESOLVE_DIRECTIVE = `resolveDirective`
|
||||||
export const APPLY_DIRECTIVES = `applyDirectives`
|
export const APPLY_DIRECTIVES = `applyDirectives`
|
||||||
export const RENDER_LIST = `renderList`
|
export const RENDER_LIST = `renderList`
|
||||||
|
export const RENDER_SLOT = `renderSlot`
|
||||||
export const TO_STRING = `toString`
|
export const TO_STRING = `toString`
|
||||||
export const MERGE_PROPS = `mergeProps`
|
export const MERGE_PROPS = `mergeProps`
|
||||||
export const TO_HANDLERS = `toHandlers`
|
export const TO_HANDLERS = `toHandlers`
|
||||||
|
@ -13,7 +13,8 @@ import {
|
|||||||
createObjectProperty,
|
createObjectProperty,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
createObjectExpression,
|
createObjectExpression,
|
||||||
Property
|
Property,
|
||||||
|
SourceLocation
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { isArray } from '@vue/shared'
|
import { isArray } from '@vue/shared'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
@ -26,6 +27,7 @@ import {
|
|||||||
TO_HANDLERS
|
TO_HANDLERS
|
||||||
} from '../runtimeConstants'
|
} from '../runtimeConstants'
|
||||||
import { getInnerRange } from '../utils'
|
import { getInnerRange } from '../utils'
|
||||||
|
import { buildSlotOutlet, buildSlots } from './vSlot'
|
||||||
|
|
||||||
const toValidId = (str: string): string => str.replace(/[^\w]/g, '')
|
const toValidId = (str: string): string => str.replace(/[^\w]/g, '')
|
||||||
|
|
||||||
@ -56,7 +58,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
]
|
]
|
||||||
// props
|
// props
|
||||||
if (hasProps) {
|
if (hasProps) {
|
||||||
const { props, directives } = buildProps(node, context)
|
const { props, directives } = buildProps(node.props, node.loc, context)
|
||||||
args.push(props)
|
args.push(props)
|
||||||
runtimeDirectives = directives
|
runtimeDirectives = directives
|
||||||
}
|
}
|
||||||
@ -94,8 +96,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
node.codegenNode = vnode
|
node.codegenNode = vnode
|
||||||
}
|
}
|
||||||
} else if (node.tagType === ElementTypes.SLOT) {
|
} else if (node.tagType === ElementTypes.SLOT) {
|
||||||
// <slot [name="xxx"]/>
|
buildSlotOutlet(node, context)
|
||||||
// TODO
|
|
||||||
}
|
}
|
||||||
// node.tagType can also be TEMPLATE, in which case nothing needs to be done
|
// node.tagType can also be TEMPLATE, in which case nothing needs to be done
|
||||||
}
|
}
|
||||||
@ -103,8 +104,9 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
|
|
||||||
type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
|
type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
|
||||||
|
|
||||||
function buildProps(
|
export function buildProps(
|
||||||
{ loc: elementLoc, props }: ElementNode,
|
props: ElementNode['props'],
|
||||||
|
elementLoc: SourceLocation,
|
||||||
context: TransformContext
|
context: TransformContext
|
||||||
): {
|
): {
|
||||||
props: PropsExpression
|
props: PropsExpression
|
||||||
@ -311,13 +313,3 @@ function createDirectiveArgs(
|
|||||||
}
|
}
|
||||||
return createArrayExpression(dirArgs, dir.loc)
|
return createArrayExpression(dirArgs, dir.loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSlots(
|
|
||||||
{ loc, children }: ElementNode,
|
|
||||||
context: TransformContext
|
|
||||||
): ObjectExpression {
|
|
||||||
const slots = createObjectExpression([], loc)
|
|
||||||
// TODO
|
|
||||||
|
|
||||||
return slots
|
|
||||||
}
|
|
||||||
|
@ -1 +1,105 @@
|
|||||||
// TODO
|
import {
|
||||||
|
ElementNode,
|
||||||
|
ObjectExpression,
|
||||||
|
createObjectExpression,
|
||||||
|
NodeTypes,
|
||||||
|
createCompoundExpression,
|
||||||
|
createCallExpression,
|
||||||
|
CompoundExpressionNode,
|
||||||
|
CallExpression
|
||||||
|
} from '../ast'
|
||||||
|
import { TransformContext } from '../transform'
|
||||||
|
import { buildProps } from './transformElement'
|
||||||
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
|
import { isSimpleIdentifier } from '../utils'
|
||||||
|
import { RENDER_SLOT } from '../runtimeConstants'
|
||||||
|
|
||||||
|
export function buildSlots(
|
||||||
|
{ loc, children }: ElementNode,
|
||||||
|
context: TransformContext
|
||||||
|
): ObjectExpression {
|
||||||
|
const slots = createObjectExpression([], loc)
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
return slots
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildSlotOutlet(node: ElementNode, context: TransformContext) {
|
||||||
|
const { props, children, loc } = node
|
||||||
|
const $slots = context.prefixIdentifiers ? `_ctx.$slots` : `$slots`
|
||||||
|
let slot: string | CompoundExpressionNode = $slots + `.default`
|
||||||
|
|
||||||
|
// check for <slot name="xxx" OR :name="xxx" />
|
||||||
|
let nameIndex: number = -1
|
||||||
|
for (let i = 0; i < props.length; i++) {
|
||||||
|
const prop = props[i]
|
||||||
|
if (prop.type === NodeTypes.ATTRIBUTE) {
|
||||||
|
if (prop.name === `name` && prop.value) {
|
||||||
|
// static name="xxx"
|
||||||
|
const name = prop.value.content
|
||||||
|
const accessor = isSimpleIdentifier(name)
|
||||||
|
? `.${name}`
|
||||||
|
: `[${JSON.stringify(name)}]`
|
||||||
|
slot = `${$slots}${accessor}`
|
||||||
|
nameIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if (prop.name === `bind`) {
|
||||||
|
const { arg, exp } = prop
|
||||||
|
if (
|
||||||
|
arg &&
|
||||||
|
exp &&
|
||||||
|
arg.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||||
|
arg.isStatic &&
|
||||||
|
arg.content === `name`
|
||||||
|
) {
|
||||||
|
// dynamic :name="xxx"
|
||||||
|
slot = createCompoundExpression(
|
||||||
|
[
|
||||||
|
$slots + `[`,
|
||||||
|
...(exp.type === NodeTypes.SIMPLE_EXPRESSION
|
||||||
|
? [exp]
|
||||||
|
: exp.children),
|
||||||
|
`]`
|
||||||
|
],
|
||||||
|
loc
|
||||||
|
)
|
||||||
|
nameIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const slotArgs: CallExpression['arguments'] = [slot]
|
||||||
|
const propsWithoutName =
|
||||||
|
nameIndex > -1
|
||||||
|
? props.slice(0, nameIndex).concat(props.slice(nameIndex + 1))
|
||||||
|
: props
|
||||||
|
const hasProps = propsWithoutName.length
|
||||||
|
if (hasProps) {
|
||||||
|
const { props: propsExpression, directives } = buildProps(
|
||||||
|
propsWithoutName,
|
||||||
|
loc,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
if (directives.length) {
|
||||||
|
context.onError(
|
||||||
|
createCompilerError(ErrorCodes.X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
slotArgs.push(propsExpression)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (children.length) {
|
||||||
|
if (!hasProps) {
|
||||||
|
slotArgs.push(`{}`)
|
||||||
|
}
|
||||||
|
slotArgs.push(children)
|
||||||
|
}
|
||||||
|
|
||||||
|
node.codegenNode = createCallExpression(
|
||||||
|
context.helper(RENDER_SLOT),
|
||||||
|
slotArgs,
|
||||||
|
loc
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
import { ComponentInternalInstance, currentInstance } from './component'
|
import { ComponentInternalInstance, currentInstance } from './component'
|
||||||
import { VNode, NormalizedChildren, normalizeVNode, VNodeChild } from './vnode'
|
import {
|
||||||
|
VNode,
|
||||||
|
NormalizedChildren,
|
||||||
|
normalizeVNode,
|
||||||
|
VNodeChild,
|
||||||
|
VNodeChildren
|
||||||
|
} from './vnode'
|
||||||
import { isArray, isFunction } from '@vue/shared'
|
import { isArray, isFunction } from '@vue/shared'
|
||||||
import { ShapeFlags } from './shapeFlags'
|
import { ShapeFlags } from './shapeFlags'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
|
|
||||||
export type Slot = (...args: any[]) => VNode[]
|
export type Slot = (...args: any[]) => VNodeChildren
|
||||||
export type Slots = Readonly<{
|
export type Slots = Readonly<{
|
||||||
[name: string]: Slot
|
[name: string]: Slot
|
||||||
}>
|
}>
|
||||||
|
12
packages/runtime-core/src/helpers/renderSlot.ts
Normal file
12
packages/runtime-core/src/helpers/renderSlot.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Slot } from '../componentSlots'
|
||||||
|
import { VNodeChildren } 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
|
||||||
|
}
|
@ -42,6 +42,7 @@ export { resolveComponent, resolveDirective } from './helpers/resolveAssets'
|
|||||||
export { renderList } from './helpers/renderList'
|
export { renderList } from './helpers/renderList'
|
||||||
export { toString } from './helpers/toString'
|
export { toString } from './helpers/toString'
|
||||||
export { toHandlers } from './helpers/toHandlers'
|
export { toHandlers } from './helpers/toHandlers'
|
||||||
|
export { renderSlot } from './helpers/renderSlot'
|
||||||
export { capitalize, camelize } from '@vue/shared'
|
export { capitalize, camelize } from '@vue/shared'
|
||||||
|
|
||||||
// Internal, for integration with runtime compiler
|
// Internal, for integration with runtime compiler
|
||||||
|
Loading…
x
Reference in New Issue
Block a user