vue3-yuanma/packages/compiler-core/src/transforms/vIf.ts

237 lines
7.2 KiB
TypeScript
Raw Normal View History

import {
createStructuralDirectiveTransform,
traverseChildren,
TransformContext
} from '../transform'
import {
NodeTypes,
ElementTypes,
ElementNode,
DirectiveNode,
IfBranchNode,
SimpleExpressionNode,
createSequenceExpression,
createCallExpression,
createConditionalExpression,
ConditionalExpression,
CallExpression,
createSimpleExpression,
JSChildNode,
ObjectExpression,
createObjectProperty,
Property,
ExpressionNode
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
2019-09-24 08:45:40 +08:00
import { processExpression } from './transformExpression'
import {
OPEN_BLOCK,
CREATE_BLOCK,
EMPTY,
FRAGMENT,
APPLY_DIRECTIVES,
MERGE_PROPS
} from '../runtimeConstants'
import { isString } from '@vue/shared'
2019-09-22 05:42:12 +08:00
export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/,
(node, dir, context) => {
if (
dir.name !== 'else' &&
(!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
) {
const loc = dir.exp ? dir.exp.loc : node.loc
context.onError(createCompilerError(ErrorCodes.X_IF_NO_EXPRESSION, loc))
dir.exp = createSimpleExpression(`true`, false, loc)
}
2019-09-24 01:29:41 +08:00
if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) {
// dir.exp can only be simple expression because vIf transform is applied
// before expression transform.
dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
}
if (dir.name === 'if') {
const branch = createIfBranch(node, dir)
const codegenNode = createSequenceExpression([
createCallExpression(context.helper(OPEN_BLOCK))
])
context.replaceNode({
type: NodeTypes.IF,
loc: node.loc,
branches: [branch],
codegenNode
})
// Exit callback. Complete the codegenNode when all children have been
// transformed.
return () => {
codegenNode.expressions.push(
createCodegenNodeForBranch(branch, 0, context)
)
}
} else {
// locate the adjacent v-if
const siblings = context.parent!.children
2019-09-20 03:41:17 +08:00
const comments = []
2019-09-23 14:52:54 +08:00
let i = siblings.indexOf(node as any)
2019-09-20 03:41:17 +08:00
while (i-- >= -1) {
const sibling = siblings[i]
2019-09-20 03:41:17 +08:00
if (__DEV__ && sibling && sibling.type === NodeTypes.COMMENT) {
context.removeNode(sibling)
comments.unshift(sibling)
continue
}
2019-09-20 03:41:17 +08:00
if (sibling && sibling.type === NodeTypes.IF) {
// move the node to the if node's branches
context.removeNode()
const branch = createIfBranch(node, dir)
2019-09-20 03:41:17 +08:00
if (__DEV__ && comments.length) {
branch.children = [...comments, ...branch.children]
}
sibling.branches.push(branch)
// since the branch was removed, it will not be traversed.
// make sure to traverse here.
traverseChildren(branch, context)
// attach this branch's codegen node to the v-if root.
let parentCondition = sibling.codegenNode
.expressions[1] as ConditionalExpression
while (true) {
if (
parentCondition.alternate.type ===
NodeTypes.JS_CONDITIONAL_EXPRESSION
) {
parentCondition = parentCondition.alternate
} else {
parentCondition.alternate = createCodegenNodeForBranch(
branch,
sibling.branches.length - 1,
context
)
break
}
}
} else {
2019-09-22 05:42:12 +08:00
context.onError(
createCompilerError(ErrorCodes.X_ELSE_NO_ADJACENT_IF, node.loc)
)
}
break
}
}
}
)
function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
return {
type: NodeTypes.IF_BRANCH,
loc: node.loc,
condition: dir.name === 'else' ? undefined : dir.exp,
children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node]
}
}
function createCodegenNodeForBranch(
branch: IfBranchNode,
index: number,
context: TransformContext
): ConditionalExpression | CallExpression {
if (branch.condition) {
return createConditionalExpression(
branch.condition,
createChildrenCodegenNode(branch, index, context),
createCallExpression(context.helper(CREATE_BLOCK), [
context.helper(EMPTY)
])
)
} else {
return createChildrenCodegenNode(branch, index, context)
}
}
function createChildrenCodegenNode(
branch: IfBranchNode,
index: number,
{ helper }: TransformContext
): CallExpression {
const keyExp = `{ key: ${index} }`
const { children } = branch
const child = children[0]
const needFragmentWrapper =
children.length !== 1 ||
child.type !== NodeTypes.ELEMENT ||
child.tagType === ElementTypes.SLOT
if (needFragmentWrapper) {
const blockArgs: CallExpression['arguments'] = [
helper(FRAGMENT),
keyExp,
children
]
if (children.length === 1) {
// optimize away nested fragments when child is a ForNode
if (child.type === NodeTypes.FOR) {
const forBlockArgs = (child.codegenNode
.expressions[1] as CallExpression).arguments
// directly use the for block's children and patchFlag
blockArgs[2] = forBlockArgs[2]
blockArgs[3] = forBlockArgs[3]
} else if (
child.type === NodeTypes.ELEMENT &&
child.tagType === ElementTypes.SLOT
) {
// <template v-if="..."><slot/></template>
// since slot always returns array, use it directly as the fragment children.
blockArgs[2] = child.codegenNode!
}
}
return createCallExpression(helper(CREATE_BLOCK), blockArgs)
} else {
const childCodegen = (child as ElementNode).codegenNode!
let vnodeCall = childCodegen
if (vnodeCall.callee.includes(APPLY_DIRECTIVES)) {
vnodeCall = vnodeCall.arguments[0] as CallExpression
}
// change child to a block
vnodeCall.callee = helper(CREATE_BLOCK)
// branch key
const existingProps = vnodeCall.arguments[1]
if (!existingProps || existingProps === `null`) {
vnodeCall.arguments[1] = keyExp
} else {
// inject branch key if not already have a key
const props = existingProps as
| CallExpression
| ObjectExpression
| ExpressionNode
if (props.type === NodeTypes.JS_CALL_EXPRESSION) {
// merged props... add ours
// only inject key to object literal if it's the first argument so that
// if doesn't override user provided keys
const first = props.arguments[0] as string | JSChildNode
if (!isString(first) && first.type === NodeTypes.JS_OBJECT_EXPRESSION) {
first.properties.unshift(createKeyProperty(index))
} else {
props.arguments.unshift(keyExp)
}
} else if (props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
props.properties.unshift(createKeyProperty(index))
} else {
// single v-bind with expression
vnodeCall.arguments[1] = createCallExpression(helper(MERGE_PROPS), [
keyExp,
props
])
}
}
return childCodegen
}
}
function createKeyProperty(index: number): Property {
return createObjectProperty(
createSimpleExpression(`key`, true),
createSimpleExpression(index + '', false)
)
}