feat(compiler): transform component slots
This commit is contained in:
parent
ee66ce78b7
commit
32666c7708
@ -3,7 +3,8 @@ import {
|
|||||||
parse,
|
parse,
|
||||||
transform,
|
transform,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
NodeTypes
|
NodeTypes,
|
||||||
|
generate
|
||||||
} from '../../src'
|
} from '../../src'
|
||||||
import { transformElement } from '../../src/transforms/transformElement'
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
import { transformOn } from '../../src/transforms/vOn'
|
import { transformOn } from '../../src/transforms/vOn'
|
||||||
@ -320,4 +321,10 @@ describe('compiler: transform slots', () => {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('generate slot', () => {
|
||||||
|
const ast = parseWithSlots(`<Comp><div/></Comp>`)
|
||||||
|
const { code } = generate(ast)
|
||||||
|
console.log(code)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -27,7 +27,8 @@ export const enum NodeTypes {
|
|||||||
JS_CALL_EXPRESSION,
|
JS_CALL_EXPRESSION,
|
||||||
JS_OBJECT_EXPRESSION,
|
JS_OBJECT_EXPRESSION,
|
||||||
JS_PROPERTY,
|
JS_PROPERTY,
|
||||||
JS_ARRAY_EXPRESSION
|
JS_ARRAY_EXPRESSION,
|
||||||
|
JS_SLOT_FUNCTION
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum ElementTypes {
|
export const enum ElementTypes {
|
||||||
@ -157,6 +158,7 @@ export type JSChildNode =
|
|||||||
| ObjectExpression
|
| ObjectExpression
|
||||||
| ArrayExpression
|
| ArrayExpression
|
||||||
| ExpressionNode
|
| ExpressionNode
|
||||||
|
| SlotFunctionExpression
|
||||||
|
|
||||||
export interface CallExpression extends Node {
|
export interface CallExpression extends Node {
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION
|
type: NodeTypes.JS_CALL_EXPRESSION
|
||||||
@ -180,6 +182,12 @@ export interface ArrayExpression extends Node {
|
|||||||
elements: Array<string | JSChildNode>
|
elements: Array<string | JSChildNode>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SlotFunctionExpression extends Node {
|
||||||
|
type: NodeTypes.JS_SLOT_FUNCTION
|
||||||
|
params: ExpressionNode | undefined
|
||||||
|
returns: ChildNode[]
|
||||||
|
}
|
||||||
|
|
||||||
export function createArrayExpression(
|
export function createArrayExpression(
|
||||||
elements: ArrayExpression['elements'],
|
elements: ArrayExpression['elements'],
|
||||||
loc: SourceLocation
|
loc: SourceLocation
|
||||||
@ -264,3 +272,16 @@ export function createCallExpression(
|
|||||||
arguments: args
|
arguments: args
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createFunctionExpression(
|
||||||
|
params: ExpressionNode | undefined,
|
||||||
|
returns: ChildNode[],
|
||||||
|
loc: SourceLocation
|
||||||
|
): SlotFunctionExpression {
|
||||||
|
return {
|
||||||
|
type: NodeTypes.JS_SLOT_FUNCTION,
|
||||||
|
params,
|
||||||
|
returns,
|
||||||
|
loc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -18,7 +18,8 @@ import {
|
|||||||
InterpolationNode,
|
InterpolationNode,
|
||||||
CompoundExpressionNode,
|
CompoundExpressionNode,
|
||||||
SimpleExpressionNode,
|
SimpleExpressionNode,
|
||||||
ElementTypes
|
ElementTypes,
|
||||||
|
SlotFunctionExpression
|
||||||
} from './ast'
|
} from './ast'
|
||||||
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
||||||
import {
|
import {
|
||||||
@ -364,10 +365,17 @@ function genNode(node: CodegenNode, context: CodegenContext) {
|
|||||||
case NodeTypes.JS_ARRAY_EXPRESSION:
|
case NodeTypes.JS_ARRAY_EXPRESSION:
|
||||||
genArrayExpression(node, context)
|
genArrayExpression(node, context)
|
||||||
break
|
break
|
||||||
|
case NodeTypes.JS_SLOT_FUNCTION:
|
||||||
|
genSlotFunction(node, context)
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore if */
|
||||||
__DEV__ &&
|
if (__DEV__) {
|
||||||
assert(false, `unhandled codegen node type: ${(node as any).type}`)
|
assert(false, `unhandled codegen node type: ${(node as any).type}`)
|
||||||
|
// make sure we exhaust all possible types
|
||||||
|
const exhaustiveCheck: never = node
|
||||||
|
return exhaustiveCheck
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -568,3 +576,14 @@ function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
|
|||||||
function genArrayExpression(node: ArrayExpression, context: CodegenContext) {
|
function genArrayExpression(node: ArrayExpression, context: CodegenContext) {
|
||||||
genNodeListAsArray(node.elements, context)
|
genNodeListAsArray(node.elements, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function genSlotFunction(
|
||||||
|
node: SlotFunctionExpression,
|
||||||
|
context: CodegenContext
|
||||||
|
) {
|
||||||
|
context.push(`(`, node)
|
||||||
|
if (node.params) genNode(node.params, context)
|
||||||
|
context.push(`) => `)
|
||||||
|
// pre-normalized slots should always return arrays
|
||||||
|
genNodeListAsArray(node.returns, context)
|
||||||
|
}
|
||||||
|
@ -69,6 +69,10 @@ export const enum ErrorCodes {
|
|||||||
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,
|
X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
|
||||||
|
X_NAMED_SLOT_ON_COMPONENT,
|
||||||
|
X_MIXED_SLOT_USAGE,
|
||||||
|
X_DUPLICATE_SLOT_NAMES,
|
||||||
|
X_EXTRANEOUS_NON_SLOT_CHILDREN,
|
||||||
|
|
||||||
// generic errors
|
// generic errors
|
||||||
X_PREFIX_ID_NOT_SUPPORTED,
|
X_PREFIX_ID_NOT_SUPPORTED,
|
||||||
@ -133,14 +137,24 @@ export const errorMessages: { [code: number]: string } = {
|
|||||||
'Note that dynamic directive argument connot contain spaces.',
|
'Note that dynamic directive argument connot contain spaces.',
|
||||||
|
|
||||||
// transform errors
|
// transform errors
|
||||||
[ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF]: `v-else-if has no adjacent v-if`,
|
[ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF]: `v-else-if has no adjacent v-if.`,
|
||||||
[ErrorCodes.X_ELSE_NO_ADJACENT_IF]: `v-else has no adjacent v-if`,
|
[ErrorCodes.X_ELSE_NO_ADJACENT_IF]: `v-else has no adjacent v-if.`,
|
||||||
[ErrorCodes.X_FOR_NO_EXPRESSION]: `v-for has no expression`,
|
[ErrorCodes.X_FOR_NO_EXPRESSION]: `v-for has no expression.`,
|
||||||
[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`,
|
[ErrorCodes.X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `Unexpected custom directive on <slot> outlet.`,
|
||||||
|
[ErrorCodes.X_NAMED_SLOT_ON_COMPONENT]:
|
||||||
|
`Named v-slot on component. ` +
|
||||||
|
`Named slots should use <template v-slot> syntax nested inside the component.`,
|
||||||
|
[ErrorCodes.X_MIXED_SLOT_USAGE]:
|
||||||
|
`Mixed v-slot usage on both the component and nested <template>.` +
|
||||||
|
`The default slot should also use <template> syntax when there are other ` +
|
||||||
|
`named slots to avoid scope ambiguity.`,
|
||||||
|
[ErrorCodes.X_DUPLICATE_SLOT_NAMES]: `Duplicate slot names found. `,
|
||||||
|
[ErrorCodes.X_EXTRANEOUS_NON_SLOT_CHILDREN]:
|
||||||
|
`Extraneous children found when component has explicit slots. ` +
|
||||||
|
`These children will be ignored.`,
|
||||||
// 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.`,
|
||||||
[ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED]: `ES module mode is not supported in this build of compiler.`
|
[ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED]: `ES module mode is not supported in this build of compiler.`
|
||||||
|
@ -141,6 +141,12 @@ export function buildProps(
|
|||||||
// directives
|
// directives
|
||||||
isStatic = false
|
isStatic = false
|
||||||
const { name, arg, exp, loc } = prop
|
const { name, arg, exp, loc } = prop
|
||||||
|
|
||||||
|
// skip v-slot - it is handled by its dedicated transform.
|
||||||
|
if (name === 'slot') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// special case for v-bind and v-on with no argument
|
// special case for v-bind and v-on with no argument
|
||||||
const isBind = name === 'bind'
|
const isBind = name === 'bind'
|
||||||
if (!arg && (isBind || name === 'on')) {
|
if (!arg && (isBind || name === 'on')) {
|
||||||
|
@ -6,22 +6,127 @@ import {
|
|||||||
createCompoundExpression,
|
createCompoundExpression,
|
||||||
createCallExpression,
|
createCallExpression,
|
||||||
CompoundExpressionNode,
|
CompoundExpressionNode,
|
||||||
CallExpression
|
CallExpression,
|
||||||
|
createObjectProperty,
|
||||||
|
createSimpleExpression,
|
||||||
|
createFunctionExpression,
|
||||||
|
DirectiveNode,
|
||||||
|
ElementTypes,
|
||||||
|
ExpressionNode,
|
||||||
|
Property,
|
||||||
|
ChildNode,
|
||||||
|
SourceLocation
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { TransformContext } from '../transform'
|
import { TransformContext } from '../transform'
|
||||||
import { buildProps } from './transformElement'
|
import { buildProps } from './transformElement'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import { isSimpleIdentifier } from '../utils'
|
import { isSimpleIdentifier } from '../utils'
|
||||||
import { RENDER_SLOT } from '../runtimeConstants'
|
import { RENDER_SLOT } from '../runtimeConstants'
|
||||||
|
import { isString } from '@vue/shared'
|
||||||
|
|
||||||
|
const isVSlot = (p: ElementNode['props'][0]): p is DirectiveNode =>
|
||||||
|
p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
|
||||||
|
|
||||||
export function buildSlots(
|
export function buildSlots(
|
||||||
{ loc, children }: ElementNode,
|
{ props, children, loc }: ElementNode,
|
||||||
context: TransformContext
|
context: TransformContext
|
||||||
): ObjectExpression {
|
): ObjectExpression {
|
||||||
const slots = createObjectExpression([], loc)
|
const slots: Property[] = []
|
||||||
// TODO
|
|
||||||
|
|
||||||
return slots
|
// 1. Check for default slot with slotProps on component itself.
|
||||||
|
// <Comp v-slot="{ prop }"/>
|
||||||
|
const explicitDefaultSlot = props.find(isVSlot)
|
||||||
|
if (explicitDefaultSlot) {
|
||||||
|
const { arg, exp, loc } = explicitDefaultSlot
|
||||||
|
if (arg) {
|
||||||
|
context.onError(
|
||||||
|
createCompilerError(ErrorCodes.X_NAMED_SLOT_ON_COMPONENT, loc)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
slots.push(buildSlot(`default`, exp, children, loc))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Iterate through children and check for template slots
|
||||||
|
// <template v-slot:foo="{ prop }">
|
||||||
|
let hasTemplateSlots = false
|
||||||
|
const seenSlotNames = new Set<string>()
|
||||||
|
const nonSlotChildren: ChildNode[] = []
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
const child = children[i]
|
||||||
|
if (
|
||||||
|
child.type === NodeTypes.ELEMENT &&
|
||||||
|
child.tagType === ElementTypes.TEMPLATE
|
||||||
|
) {
|
||||||
|
const { props, children, loc: nodeLoc } = child
|
||||||
|
const slotDir = props.find(isVSlot)
|
||||||
|
if (slotDir) {
|
||||||
|
hasTemplateSlots = true
|
||||||
|
const { arg: slotName, exp: slotProps, loc: dirLoc } = slotDir
|
||||||
|
if (explicitDefaultSlot) {
|
||||||
|
// already has on-component default slot - this is incorrect usage.
|
||||||
|
context.onError(
|
||||||
|
createCompilerError(ErrorCodes.X_MIXED_SLOT_USAGE, dirLoc)
|
||||||
|
)
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
// check duplicate slot names
|
||||||
|
if (
|
||||||
|
!slotName ||
|
||||||
|
(slotName.type === NodeTypes.SIMPLE_EXPRESSION && slotName.isStatic)
|
||||||
|
) {
|
||||||
|
const name = slotName ? slotName.content : `default`
|
||||||
|
if (seenSlotNames.has(name)) {
|
||||||
|
context.onError(
|
||||||
|
createCompilerError(ErrorCodes.X_DUPLICATE_SLOT_NAMES, dirLoc)
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seenSlotNames.add(name)
|
||||||
|
}
|
||||||
|
slots.push(
|
||||||
|
buildSlot(slotName || `default`, slotProps, children, nodeLoc)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nonSlotChildren.push(child)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nonSlotChildren.push(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasTemplateSlots && nonSlotChildren.length) {
|
||||||
|
context.onError(
|
||||||
|
createCompilerError(
|
||||||
|
ErrorCodes.X_EXTRANEOUS_NON_SLOT_CHILDREN,
|
||||||
|
nonSlotChildren[0].loc
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!explicitDefaultSlot && !hasTemplateSlots) {
|
||||||
|
// implicit default slot.
|
||||||
|
slots.push(buildSlot(`default`, undefined, children, loc))
|
||||||
|
}
|
||||||
|
|
||||||
|
return createObjectExpression(slots, loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSlot(
|
||||||
|
name: string | ExpressionNode,
|
||||||
|
slotProps: ExpressionNode | undefined,
|
||||||
|
children: ChildNode[],
|
||||||
|
loc: SourceLocation
|
||||||
|
): Property {
|
||||||
|
return createObjectProperty(
|
||||||
|
isString(name) ? createSimpleExpression(name, true, loc) : name,
|
||||||
|
createFunctionExpression(
|
||||||
|
slotProps,
|
||||||
|
children,
|
||||||
|
children.length ? children[0].loc : loc
|
||||||
|
),
|
||||||
|
loc
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildSlotOutlet(node: ElementNode, context: TransformContext) {
|
export function buildSlotOutlet(node: ElementNode, context: TransformContext) {
|
||||||
@ -84,7 +189,10 @@ export function buildSlotOutlet(node: ElementNode, context: TransformContext) {
|
|||||||
)
|
)
|
||||||
if (directives.length) {
|
if (directives.length) {
|
||||||
context.onError(
|
context.onError(
|
||||||
createCompilerError(ErrorCodes.X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET)
|
createCompilerError(
|
||||||
|
ErrorCodes.X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
|
||||||
|
directives[0].loc
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
slotArgs.push(propsExpression)
|
slotArgs.push(propsExpression)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user