2019-09-23 00:55:18 +00:00
|
|
|
import {
|
|
|
|
createStructuralDirectiveTransform,
|
2020-02-14 20:57:14 +00:00
|
|
|
TransformContext,
|
|
|
|
traverseNode
|
2019-09-23 00:55:18 +00:00
|
|
|
} from '../transform'
|
2019-09-17 23:08:47 +00:00
|
|
|
import {
|
|
|
|
NodeTypes,
|
|
|
|
ElementTypes,
|
|
|
|
ElementNode,
|
|
|
|
DirectiveNode,
|
2019-09-27 15:42:02 +00:00
|
|
|
IfBranchNode,
|
2019-10-01 16:25:13 +00:00
|
|
|
SimpleExpressionNode,
|
|
|
|
createCallExpression,
|
|
|
|
createConditionalExpression,
|
|
|
|
createSimpleExpression,
|
|
|
|
createObjectProperty,
|
2019-10-06 02:47:20 +00:00
|
|
|
createObjectExpression,
|
|
|
|
IfConditionalExpression,
|
|
|
|
BlockCodegenNode,
|
2020-02-11 23:12:56 +00:00
|
|
|
IfNode,
|
2020-08-04 16:01:07 +00:00
|
|
|
createVNodeCall,
|
2020-08-20 15:43:34 +00:00
|
|
|
AttributeNode,
|
2020-10-05 15:58:37 +00:00
|
|
|
locStub,
|
2020-11-21 00:26:07 +00:00
|
|
|
CacheExpression,
|
|
|
|
ConstantTypes
|
2019-09-17 23:08:47 +00:00
|
|
|
} from '../ast'
|
|
|
|
import { createCompilerError, ErrorCodes } from '../errors'
|
2019-09-24 00:45:40 +00:00
|
|
|
import { processExpression } from './transformExpression'
|
2020-06-11 20:31:51 +00:00
|
|
|
import { validateBrowserExpression } from '../validateExpression'
|
2019-10-01 16:25:13 +00:00
|
|
|
import {
|
|
|
|
CREATE_BLOCK,
|
|
|
|
FRAGMENT,
|
2020-02-11 23:12:56 +00:00
|
|
|
CREATE_COMMENT,
|
2020-09-14 16:41:35 +00:00
|
|
|
OPEN_BLOCK
|
2019-10-05 21:18:25 +00:00
|
|
|
} from '../runtimeHelpers'
|
2020-07-28 19:18:41 +00:00
|
|
|
import { injectProp, findDir, findProp } from '../utils'
|
2020-03-17 15:36:56 +00:00
|
|
|
import { PatchFlags, PatchFlagNames } from '@vue/shared'
|
2019-09-17 23:08:47 +00:00
|
|
|
|
2019-09-21 21:42:12 +00:00
|
|
|
export const transformIf = createStructuralDirectiveTransform(
|
2019-09-17 23:08:47 +00:00
|
|
|
/^(if|else|else-if)$/,
|
|
|
|
(node, dir, context) => {
|
2020-02-06 02:04:40 +00:00
|
|
|
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
|
2020-07-15 13:21:40 +00:00
|
|
|
// #1587: We need to dynamically increment the key based on the current
|
|
|
|
// node's sibling nodes, since chained v-if/else branches are
|
|
|
|
// rendered at the same depth
|
|
|
|
const siblings = context.parent!.children
|
|
|
|
let i = siblings.indexOf(ifNode)
|
|
|
|
let key = 0
|
|
|
|
while (i-- >= 0) {
|
|
|
|
const sibling = siblings[i]
|
|
|
|
if (sibling && sibling.type === NodeTypes.IF) {
|
|
|
|
key += sibling.branches.length
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-01 16:25:13 +00:00
|
|
|
// Exit callback. Complete the codegenNode when all children have been
|
|
|
|
// transformed.
|
|
|
|
return () => {
|
2020-02-03 20:51:41 +00:00
|
|
|
if (isRoot) {
|
2020-02-11 23:12:56 +00:00
|
|
|
ifNode.codegenNode = createCodegenNodeForBranch(
|
|
|
|
branch,
|
2020-07-15 13:21:40 +00:00
|
|
|
key,
|
2020-02-11 23:12:56 +00:00
|
|
|
context
|
|
|
|
) as IfConditionalExpression
|
2020-02-03 20:51:41 +00:00
|
|
|
} else {
|
2019-10-01 16:25:13 +00:00
|
|
|
// attach this branch's codegen node to the v-if root.
|
2020-10-05 15:58:37 +00:00
|
|
|
const parentCondition = getParentCondition(ifNode.codegenNode!)
|
2020-01-31 14:43:34 +00:00
|
|
|
parentCondition.alternate = createCodegenNodeForBranch(
|
|
|
|
branch,
|
2020-07-15 13:21:40 +00:00
|
|
|
key + ifNode.branches.length - 1,
|
2020-01-31 14:43:34 +00:00
|
|
|
context
|
|
|
|
)
|
2019-09-17 23:08:47 +00:00
|
|
|
}
|
|
|
|
}
|
2020-02-03 20:51:41 +00:00
|
|
|
})
|
2019-09-17 23:08:47 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2020-02-03 22:47:06 +00:00
|
|
|
// target-agnostic transform used for both Client and SSR
|
2020-02-06 02:04:40 +00:00
|
|
|
export function processIf(
|
2020-02-03 20:51:41 +00:00
|
|
|
node: ElementNode,
|
|
|
|
dir: DirectiveNode,
|
|
|
|
context: TransformContext,
|
|
|
|
processCodegen?: (
|
|
|
|
node: IfNode,
|
|
|
|
branch: IfBranchNode,
|
|
|
|
isRoot: boolean
|
2020-02-03 22:47:06 +00:00
|
|
|
) => (() => void) | undefined
|
|
|
|
) {
|
2020-02-03 20:51:41 +00:00
|
|
|
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_V_IF_NO_EXPRESSION, dir.loc)
|
|
|
|
)
|
|
|
|
dir.exp = createSimpleExpression(`true`, false, loc)
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2020-06-11 20:31:51 +00:00
|
|
|
if (__DEV__ && __BROWSER__ && dir.exp) {
|
|
|
|
validateBrowserExpression(dir.exp as SimpleExpressionNode, context)
|
|
|
|
}
|
|
|
|
|
2020-02-03 20:51:41 +00:00
|
|
|
if (dir.name === 'if') {
|
|
|
|
const branch = createIfBranch(node, dir)
|
|
|
|
const ifNode: IfNode = {
|
|
|
|
type: NodeTypes.IF,
|
|
|
|
loc: node.loc,
|
|
|
|
branches: [branch]
|
|
|
|
}
|
|
|
|
context.replaceNode(ifNode)
|
|
|
|
if (processCodegen) {
|
|
|
|
return processCodegen(ifNode, branch, true)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// locate the adjacent v-if
|
|
|
|
const siblings = context.parent!.children
|
|
|
|
const comments = []
|
|
|
|
let i = siblings.indexOf(node)
|
|
|
|
while (i-- >= -1) {
|
|
|
|
const sibling = siblings[i]
|
|
|
|
if (__DEV__ && sibling && sibling.type === NodeTypes.COMMENT) {
|
|
|
|
context.removeNode(sibling)
|
|
|
|
comments.unshift(sibling)
|
|
|
|
continue
|
|
|
|
}
|
2020-10-08 00:57:17 +00:00
|
|
|
|
|
|
|
if (
|
|
|
|
sibling &&
|
|
|
|
sibling.type === NodeTypes.TEXT &&
|
|
|
|
!sibling.content.trim().length
|
|
|
|
) {
|
|
|
|
context.removeNode(sibling)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-02-03 20:51:41 +00:00
|
|
|
if (sibling && sibling.type === NodeTypes.IF) {
|
|
|
|
// move the node to the if node's branches
|
|
|
|
context.removeNode()
|
|
|
|
const branch = createIfBranch(node, dir)
|
|
|
|
if (__DEV__ && comments.length) {
|
|
|
|
branch.children = [...comments, ...branch.children]
|
|
|
|
}
|
2020-08-04 16:01:07 +00:00
|
|
|
|
|
|
|
// check if user is forcing same key on different branches
|
|
|
|
if (__DEV__ || !__BROWSER__) {
|
|
|
|
const key = branch.userKey
|
|
|
|
if (key) {
|
|
|
|
sibling.branches.forEach(({ userKey }) => {
|
|
|
|
if (isSameKey(userKey, key)) {
|
|
|
|
context.onError(
|
|
|
|
createCompilerError(
|
|
|
|
ErrorCodes.X_V_IF_SAME_KEY,
|
|
|
|
branch.userKey!.loc
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-03 20:51:41 +00:00
|
|
|
sibling.branches.push(branch)
|
|
|
|
const onExit = processCodegen && processCodegen(sibling, branch, false)
|
|
|
|
// since the branch was removed, it will not be traversed.
|
|
|
|
// make sure to traverse here.
|
2020-02-14 20:57:14 +00:00
|
|
|
traverseNode(branch, context)
|
2020-02-03 20:51:41 +00:00
|
|
|
// call on exit
|
|
|
|
if (onExit) onExit()
|
|
|
|
// make sure to reset currentNode after traversal to indicate this
|
|
|
|
// node has been removed.
|
|
|
|
context.currentNode = null
|
|
|
|
} else {
|
|
|
|
context.onError(
|
|
|
|
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-25 23:17:45 +00:00
|
|
|
function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
|
2019-09-17 23:08:47 +00:00
|
|
|
return {
|
|
|
|
type: NodeTypes.IF_BRANCH,
|
|
|
|
loc: node.loc,
|
|
|
|
condition: dir.name === 'else' ? undefined : dir.exp,
|
2020-07-19 18:36:01 +00:00
|
|
|
children:
|
|
|
|
node.tagType === ElementTypes.TEMPLATE && !findDir(node, 'for')
|
|
|
|
? node.children
|
2020-08-04 16:01:07 +00:00
|
|
|
: [node],
|
|
|
|
userKey: findProp(node, `key`)
|
2019-09-17 23:08:47 +00:00
|
|
|
}
|
|
|
|
}
|
2019-10-01 16:25:13 +00:00
|
|
|
|
|
|
|
function createCodegenNodeForBranch(
|
2019-10-01 19:04:58 +00:00
|
|
|
branch: IfBranchNode,
|
2020-07-28 19:18:41 +00:00
|
|
|
keyIndex: number,
|
2019-10-01 16:25:13 +00:00
|
|
|
context: TransformContext
|
2019-10-06 02:47:20 +00:00
|
|
|
): IfConditionalExpression | BlockCodegenNode {
|
2019-10-01 19:04:58 +00:00
|
|
|
if (branch.condition) {
|
2019-10-01 16:25:13 +00:00
|
|
|
return createConditionalExpression(
|
2019-10-01 19:04:58 +00:00
|
|
|
branch.condition,
|
2020-07-28 19:18:41 +00:00
|
|
|
createChildrenCodegenNode(branch, keyIndex, context),
|
2019-10-25 01:19:02 +00:00
|
|
|
// make sure to pass in asBlock: true so that the comment node call
|
|
|
|
// closes the current block.
|
|
|
|
createCallExpression(context.helper(CREATE_COMMENT), [
|
|
|
|
__DEV__ ? '"v-if"' : '""',
|
|
|
|
'true'
|
|
|
|
])
|
2019-10-06 02:47:20 +00:00
|
|
|
) as IfConditionalExpression
|
2019-10-01 16:25:13 +00:00
|
|
|
} else {
|
2020-07-28 19:18:41 +00:00
|
|
|
return createChildrenCodegenNode(branch, keyIndex, context)
|
2019-10-01 16:25:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function createChildrenCodegenNode(
|
2019-10-01 19:04:58 +00:00
|
|
|
branch: IfBranchNode,
|
2020-07-28 19:18:41 +00:00
|
|
|
keyIndex: number,
|
2019-10-03 16:03:14 +00:00
|
|
|
context: TransformContext
|
2020-02-11 23:12:56 +00:00
|
|
|
): BlockCodegenNode {
|
2019-10-03 16:03:14 +00:00
|
|
|
const { helper } = context
|
|
|
|
const keyProperty = createObjectProperty(
|
|
|
|
`key`,
|
2020-11-21 00:26:07 +00:00
|
|
|
createSimpleExpression(
|
|
|
|
`${keyIndex}`,
|
|
|
|
false,
|
|
|
|
locStub,
|
|
|
|
ConstantTypes.CAN_HOIST
|
|
|
|
)
|
2019-10-03 16:03:14 +00:00
|
|
|
)
|
2019-10-01 20:48:20 +00:00
|
|
|
const { children } = branch
|
2020-02-03 20:51:41 +00:00
|
|
|
const firstChild = children[0]
|
2019-10-01 20:48:20 +00:00
|
|
|
const needFragmentWrapper =
|
2020-02-03 20:51:41 +00:00
|
|
|
children.length !== 1 || firstChild.type !== NodeTypes.ELEMENT
|
2019-10-01 20:48:20 +00:00
|
|
|
if (needFragmentWrapper) {
|
2020-02-03 20:51:41 +00:00
|
|
|
if (children.length === 1 && firstChild.type === NodeTypes.FOR) {
|
2019-10-02 03:53:52 +00:00
|
|
|
// optimize away nested fragments when child is a ForNode
|
2020-02-11 23:12:56 +00:00
|
|
|
const vnodeCall = firstChild.codegenNode!
|
|
|
|
injectProp(vnodeCall, keyProperty, context)
|
|
|
|
return vnodeCall
|
|
|
|
} else {
|
2021-03-25 19:43:44 +00:00
|
|
|
let patchFlag = PatchFlags.STABLE_FRAGMENT
|
|
|
|
let patchFlagText = PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
|
|
|
|
// check if the fragment actually contains a single valid child with
|
|
|
|
// the rest being comments
|
|
|
|
if (
|
|
|
|
__DEV__ &&
|
|
|
|
children.filter(c => c.type !== NodeTypes.COMMENT).length === 1
|
|
|
|
) {
|
|
|
|
patchFlag |= PatchFlags.DEV_ROOT_FRAGMENT
|
|
|
|
patchFlagText += `, ${PatchFlagNames[PatchFlags.DEV_ROOT_FRAGMENT]}`
|
|
|
|
}
|
|
|
|
|
2020-02-11 23:12:56 +00:00
|
|
|
return createVNodeCall(
|
|
|
|
context,
|
|
|
|
helper(FRAGMENT),
|
|
|
|
createObjectExpression([keyProperty]),
|
|
|
|
children,
|
2021-03-25 19:43:44 +00:00
|
|
|
patchFlag + (__DEV__ ? ` /* ${patchFlagText} */` : ``),
|
2020-02-11 23:12:56 +00:00
|
|
|
undefined,
|
|
|
|
undefined,
|
|
|
|
true,
|
|
|
|
false,
|
|
|
|
branch.loc
|
|
|
|
)
|
2019-10-01 20:48:20 +00:00
|
|
|
}
|
2019-10-01 16:25:13 +00:00
|
|
|
} else {
|
2020-02-11 23:12:56 +00:00
|
|
|
const vnodeCall = (firstChild as ElementNode)
|
|
|
|
.codegenNode as BlockCodegenNode
|
2019-10-03 16:03:14 +00:00
|
|
|
// Change createVNode to createBlock.
|
2020-09-14 16:41:35 +00:00
|
|
|
if (vnodeCall.type === NodeTypes.VNODE_CALL) {
|
2020-02-11 23:12:56 +00:00
|
|
|
vnodeCall.isBlock = true
|
|
|
|
helper(OPEN_BLOCK)
|
|
|
|
helper(CREATE_BLOCK)
|
2019-10-01 16:25:13 +00:00
|
|
|
}
|
2019-10-03 16:03:14 +00:00
|
|
|
// inject branch key
|
2019-10-08 14:50:00 +00:00
|
|
|
injectProp(vnodeCall, keyProperty, context)
|
2020-02-11 23:12:56 +00:00
|
|
|
return vnodeCall
|
2019-10-01 16:25:13 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-04 16:01:07 +00:00
|
|
|
|
|
|
|
function isSameKey(
|
|
|
|
a: AttributeNode | DirectiveNode | undefined,
|
|
|
|
b: AttributeNode | DirectiveNode
|
|
|
|
): boolean {
|
|
|
|
if (!a || a.type !== b.type) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if (a.type === NodeTypes.ATTRIBUTE) {
|
|
|
|
if (a.value!.content !== (b as AttributeNode).value!.content) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// directive
|
|
|
|
const exp = a.exp!
|
|
|
|
const branchExp = (b as DirectiveNode).exp!
|
|
|
|
if (exp.type !== branchExp.type) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
exp.type !== NodeTypes.SIMPLE_EXPRESSION ||
|
|
|
|
(exp.isStatic !== (branchExp as SimpleExpressionNode).isStatic ||
|
|
|
|
exp.content !== (branchExp as SimpleExpressionNode).content)
|
|
|
|
) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
2020-10-05 15:58:37 +00:00
|
|
|
|
|
|
|
function getParentCondition(
|
|
|
|
node: IfConditionalExpression | CacheExpression
|
|
|
|
): IfConditionalExpression {
|
|
|
|
while (true) {
|
|
|
|
if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {
|
|
|
|
if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {
|
|
|
|
node = node.alternate
|
|
|
|
} else {
|
|
|
|
return node
|
|
|
|
}
|
|
|
|
} else if (node.type === NodeTypes.JS_CACHE_EXPRESSION) {
|
|
|
|
node = node.value as IfConditionalExpression
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|