feat(compiler): transform slot outlets

This commit is contained in:
Evan You
2019-09-27 20:29:20 -04:00
parent d900c13efb
commit ee66ce78b7
10 changed files with 480 additions and 28 deletions

View File

@@ -160,8 +160,8 @@ export type JSChildNode =
export interface CallExpression extends Node {
type: NodeTypes.JS_CALL_EXPRESSION
callee: string // can only be imported runtime helpers, so no source location
arguments: Array<string | JSChildNode | ChildNode[]>
callee: string | ExpressionNode
arguments: (string | JSChildNode | ChildNode[])[]
}
export interface ObjectExpression extends Node {
@@ -253,7 +253,7 @@ export function createCompoundExpression(
}
export function createCallExpression(
callee: string,
callee: string | ExpressionNode,
args: CallExpression['arguments'],
loc: SourceLocation
): CallExpression {

View File

@@ -17,7 +17,8 @@ import {
Position,
InterpolationNode,
CompoundExpressionNode,
SimpleExpressionNode
SimpleExpressionNode,
ElementTypes
} from './ast'
import { SourceMapGenerator, RawSourceMap } from 'source-map'
import {
@@ -262,7 +263,10 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
// This will generate a single vnode call if:
// - The target position explicitly allows a single node (root, if, for)
// - The list has length === 1, AND The only child is a text, 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(
children: ChildNode[],
context: CodegenContext,
@@ -272,12 +276,14 @@ function genChildren(
return context.push(`null`)
}
const child = children[0]
const type = child.type
if (
children.length === 1 &&
(allowSingle ||
child.type === NodeTypes.TEXT ||
child.type === NodeTypes.INTERPOLATION ||
child.type === NodeTypes.COMMENT)
type === NodeTypes.TEXT ||
type === NodeTypes.INTERPOLATION ||
(type === NodeTypes.ELEMENT &&
(child as ElementNode).tagType === ElementTypes.SLOT))
) {
genNode(child, context)
} else {
@@ -523,7 +529,12 @@ function genCallExpression(
context: CodegenContext,
multilines = node.arguments.length > 2
) {
context.push(node.callee + `(`, node, true)
if (isString(node.callee)) {
context.push(node.callee + `(`, node, true)
} else {
genNode(node.callee, context)
context.push(`(`)
}
multilines && context.indent()
genNodeList(node.arguments, context, multilines)
multilines && context.deindent()

View File

@@ -68,6 +68,7 @@ export const enum ErrorCodes {
X_FOR_MALFORMED_EXPRESSION,
X_V_BIND_NO_EXPRESSION,
X_V_ON_NO_EXPRESSION,
X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
// generic errors
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_V_BIND_NO_EXPRESSION]: `v-bind 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
[ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,

View File

@@ -10,6 +10,7 @@ export const RESOLVE_COMPONENT = `resolveComponent`
export const RESOLVE_DIRECTIVE = `resolveDirective`
export const APPLY_DIRECTIVES = `applyDirectives`
export const RENDER_LIST = `renderList`
export const RENDER_SLOT = `renderSlot`
export const TO_STRING = `toString`
export const MERGE_PROPS = `mergeProps`
export const TO_HANDLERS = `toHandlers`

View File

@@ -13,7 +13,8 @@ import {
createObjectProperty,
createSimpleExpression,
createObjectExpression,
Property
Property,
SourceLocation
} from '../ast'
import { isArray } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
@@ -26,6 +27,7 @@ import {
TO_HANDLERS
} from '../runtimeConstants'
import { getInnerRange } from '../utils'
import { buildSlotOutlet, buildSlots } from './vSlot'
const toValidId = (str: string): string => str.replace(/[^\w]/g, '')
@@ -56,7 +58,7 @@ export const transformElement: NodeTransform = (node, context) => {
]
// props
if (hasProps) {
const { props, directives } = buildProps(node, context)
const { props, directives } = buildProps(node.props, node.loc, context)
args.push(props)
runtimeDirectives = directives
}
@@ -94,8 +96,7 @@ export const transformElement: NodeTransform = (node, context) => {
node.codegenNode = vnode
}
} else if (node.tagType === ElementTypes.SLOT) {
// <slot [name="xxx"]/>
// TODO
buildSlotOutlet(node, context)
}
// 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
function buildProps(
{ loc: elementLoc, props }: ElementNode,
export function buildProps(
props: ElementNode['props'],
elementLoc: SourceLocation,
context: TransformContext
): {
props: PropsExpression
@@ -311,13 +313,3 @@ function createDirectiveArgs(
}
return createArrayExpression(dirArgs, dir.loc)
}
function buildSlots(
{ loc, children }: ElementNode,
context: TransformContext
): ObjectExpression {
const slots = createObjectExpression([], loc)
// TODO
return slots
}

View File

@@ -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
)
}