feat(compiler): element transform

This commit is contained in:
Evan You
2019-09-21 17:42:12 -04:00
parent 93440bba97
commit baa8954884
13 changed files with 349 additions and 160 deletions

View File

@@ -1,29 +1,43 @@
import { Transform, TransformContext } from '../transform'
import { NodeTransform, TransformContext } from '../transform'
import {
NodeTypes,
ElementTypes,
CallExpression,
ObjectExpression,
ElementNode
ElementNode,
DirectiveNode,
ExpressionNode,
ArrayExpression,
createCallExpression,
createArrayExpression,
createObjectProperty,
createExpression,
createObjectExpression
} from '../ast'
import { isArray } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
// generate a JavaScript AST for this element's codegen
export const prepareElementForCodegen: Transform = (node, context) => {
export const prepareElementForCodegen: NodeTransform = (node, context) => {
if (node.type === NodeTypes.ELEMENT) {
if (
node.tagType === ElementTypes.ELEMENT ||
node.tagType === ElementTypes.COMPONENT
) {
const isComponent = node.tagType === ElementTypes.ELEMENT
const hasProps = node.attrs.length > 0 || node.directives.length > 0
const hasProps = node.props.length > 0
const hasChildren = node.children.length > 0
let runtimeDirectives: DirectiveNode[] | undefined
const args: CallExpression['arguments'] = [
// TODO inject resolveComponent dep to root
isComponent ? node.tag : `"${node.tag}"`
]
// props
if (hasProps) {
args.push(buildProps(node))
const { props, directives } = buildProps(node, context)
args.push(props)
runtimeDirectives = directives
}
// children
if (hasChildren) {
@@ -34,53 +48,155 @@ export const prepareElementForCodegen: Transform = (node, context) => {
args.push(isComponent ? buildSlots(node, context) : node.children)
}
node.codegenNode = {
type: NodeTypes.CALL_EXPRESSION,
loc: node.loc,
callee: `h`,
arguments: args
const { loc } = node
const vnode = createCallExpression(`h`, args, loc)
if (runtimeDirectives) {
node.codegenNode = createCallExpression(
`applyDirectives`,
[
vnode,
createArrayExpression(
runtimeDirectives.map(dir => {
return createDirectiveArgs(dir, context)
}),
loc
)
],
loc
)
} else {
node.codegenNode = vnode
}
} else if (node.tagType === ElementTypes.SLOT) {
// <slot [name="xxx"]/>
// TODO
} else if (node.tagType === ElementTypes.TEMPLATE) {
// do nothing
}
}
}
function buildProps({ loc, attrs }: ElementNode): ObjectExpression {
return {
type: NodeTypes.OBJECT_EXPRESSION,
loc,
// At this stage we will only process static attrs. Directive bindings will
// be handled by their respective transforms which adds/modifies the props.
properties: attrs.map(({ name, value, loc }) => {
return {
type: NodeTypes.PROPERTY,
loc,
key: {
type: NodeTypes.EXPRESSION,
loc,
content: name,
isStatic: true
},
value: {
type: NodeTypes.EXPRESSION,
loc: value ? value.loc : loc,
content: value ? value.content : '',
isStatic: true
function buildProps(
{ loc, props }: ElementNode,
context: TransformContext
): {
props: ObjectExpression | CallExpression
directives: DirectiveNode[]
} {
let properties: ObjectExpression['properties'] = []
const mergeArgs: Array<ObjectExpression | ExpressionNode> = []
const runtimeDirectives: DirectiveNode[] = []
for (let i = 0; i < props.length; i++) {
// static attribute
const prop = props[i]
if (prop.type === NodeTypes.ATTRIBUTE) {
const { loc, name, value } = prop
properties.push(
createObjectProperty(
createExpression(name, true, loc),
createExpression(
value ? value.content : '',
true,
value ? value.loc : loc
),
loc
)
)
} else {
// directives
// special case for v-bind with no argument
if (prop.name === 'bind' && !prop.arg) {
if (prop.exp) {
if (properties.length) {
mergeArgs.push(createObjectExpression(properties, loc))
properties = []
}
mergeArgs.push(prop.exp)
} else {
context.onError(
createCompilerError(
ErrorCodes.X_V_BIND_NO_EXPRESSION,
prop.loc.start
)
)
}
continue
}
})
const directiveTransform = context.directiveTransforms[prop.name]
if (directiveTransform) {
const { props, needRuntime } = directiveTransform(prop, context)
if (isArray(props)) {
properties.push(...props)
} else {
properties.push(props)
}
if (needRuntime) {
runtimeDirectives.push(prop)
}
} else {
// no built-in transform, this is a user custom directive.
runtimeDirectives.push(prop)
}
}
}
let ret: ObjectExpression | CallExpression
// has v-bind="object", wrap with mergeProps
if (mergeArgs.length) {
if (properties.length) {
mergeArgs.push(createObjectExpression(properties, loc))
}
if (mergeArgs.length > 1) {
ret = createCallExpression(`mergeProps`, mergeArgs, loc)
} else {
// single v-bind with nothing else - no need for a mergeProps call
ret = createObjectExpression(properties, loc)
}
} else {
ret = createObjectExpression(properties, loc)
}
return {
props: ret,
directives: runtimeDirectives
}
}
function createDirectiveArgs(
dir: DirectiveNode,
context: TransformContext
): ArrayExpression {
// TODO inject resolveDirective dep to root
const dirArgs: ArrayExpression['elements'] = [dir.name]
const { loc } = dir
if (dir.exp) dirArgs.push(dir.exp)
if (dir.arg) dirArgs.push(dir.arg)
if (Object.keys(dir.modifiers).length) {
dirArgs.push(
createObjectExpression(
dir.modifiers.map(modifier =>
createObjectProperty(
createExpression(modifier, true, loc),
createExpression(`true`, false, loc),
loc
)
),
loc
)
)
}
return createArrayExpression(dirArgs, dir.loc)
}
function buildSlots(
{ loc, children }: ElementNode,
context: TransformContext
): ObjectExpression {
const slots: ObjectExpression = {
type: NodeTypes.OBJECT_EXPRESSION,
loc,
properties: []
}
const slots = createObjectExpression([], loc)
// TODO
return slots

View File

@@ -1,5 +1,5 @@
import { createDirectiveTransform } from '../transform'
import { NodeTypes, ExpressionNode } from '../ast'
import { createStructuralDirectiveTransform } from '../transform'
import { NodeTypes, ExpressionNode, createExpression } from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { getInnerRange } from '../utils'
@@ -7,7 +7,7 @@ const forAliasRE = /([\s\S]*?)(?:(?<=\))|\s+)(?:in|of)\s+([\s\S]*)/
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
const stripParensRE = /^\(|\)$/g
export const transformFor = createDirectiveTransform(
export const transformFor = createStructuralDirectiveTransform(
'for',
(node, dir, context) => {
if (dir.exp) {
@@ -27,7 +27,7 @@ export const transformFor = createDirectiveTransform(
children: [node]
})
} else {
context.emitError(
context.onError(
createCompilerError(
ErrorCodes.X_FOR_MALFORMED_EXPRESSION,
dir.loc.start
@@ -35,7 +35,7 @@ export const transformFor = createDirectiveTransform(
)
}
} else {
context.emitError(
context.onError(
createCompilerError(ErrorCodes.X_FOR_NO_EXPRESSION, dir.loc.start)
)
}
@@ -118,11 +118,10 @@ function maybeCreateExpression(
node: ExpressionNode
): ExpressionNode | undefined {
if (alias) {
return {
type: NodeTypes.EXPRESSION,
loc: getInnerRange(node.loc, alias.offset, alias.content.length),
content: alias.content,
isStatic: false
}
return createExpression(
alias.content,
false,
getInnerRange(node.loc, alias.offset, alias.content.length)
)
}
}

View File

@@ -1,4 +1,4 @@
import { createDirectiveTransform } from '../transform'
import { createStructuralDirectiveTransform } from '../transform'
import {
NodeTypes,
ElementTypes,
@@ -8,7 +8,7 @@ import {
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
export const transformIf = createDirectiveTransform(
export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/,
(node, dir, context) => {
if (dir.name === 'if') {
@@ -38,7 +38,7 @@ export const transformIf = createDirectiveTransform(
}
sibling.branches.push(branch)
} else {
context.emitError(
context.onError(
createCompilerError(
dir.name === 'else'
? ErrorCodes.X_ELSE_NO_ADJACENT_IF

View File

@@ -1 +0,0 @@
// TODO