| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 |
- import {
- createStructuralDirectiveTransform,
- TransformContext,
- traverseNode
- } from '../transform'
- import {
- NodeTypes,
- ElementTypes,
- ElementNode,
- DirectiveNode,
- IfBranchNode,
- SimpleExpressionNode,
- createCallExpression,
- createConditionalExpression,
- createSimpleExpression,
- createObjectProperty,
- createObjectExpression,
- IfConditionalExpression,
- BlockCodegenNode,
- IfNode,
- createVNodeCall,
- AttributeNode,
- locStub,
- CacheExpression,
- ConstantTypes
- } from '../ast'
- import { createCompilerError, ErrorCodes } from '../errors'
- import { processExpression } from './transformExpression'
- import { validateBrowserExpression } from '../validateExpression'
- import {
- CREATE_BLOCK,
- FRAGMENT,
- CREATE_COMMENT,
- OPEN_BLOCK
- } from '../runtimeHelpers'
- import { injectProp, findDir, findProp } from '../utils'
- import { PatchFlags, PatchFlagNames } from '@vue/shared'
- export const transformIf = createStructuralDirectiveTransform(
- /^(if|else|else-if)$/,
- (node, dir, context) => {
- return processIf(node, dir, context, (ifNode, branch, isRoot) => {
- // #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
- }
- }
- // Exit callback. Complete the codegenNode when all children have been
- // transformed.
- return () => {
- if (isRoot) {
- ifNode.codegenNode = createCodegenNodeForBranch(
- branch,
- key,
- context
- ) as IfConditionalExpression
- } else {
- // attach this branch's codegen node to the v-if root.
- const parentCondition = getParentCondition(ifNode.codegenNode!)
- parentCondition.alternate = createCodegenNodeForBranch(
- branch,
- key + ifNode.branches.length - 1,
- context
- )
- }
- }
- })
- }
- )
- // target-agnostic transform used for both Client and SSR
- export function processIf(
- node: ElementNode,
- dir: DirectiveNode,
- context: TransformContext,
- processCodegen?: (
- node: IfNode,
- branch: IfBranchNode,
- isRoot: boolean
- ) => (() => void) | undefined
- ) {
- 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)
- }
- if (__DEV__ && __BROWSER__ && dir.exp) {
- validateBrowserExpression(dir.exp as SimpleExpressionNode, context)
- }
- 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
- }
- if (
- sibling &&
- sibling.type === NodeTypes.TEXT &&
- !sibling.content.trim().length
- ) {
- context.removeNode(sibling)
- continue
- }
- 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]
- }
- // 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
- )
- )
- }
- })
- }
- }
- 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.
- traverseNode(branch, context)
- // 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
- }
- }
- }
- 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 && !findDir(node, 'for')
- ? node.children
- : [node],
- userKey: findProp(node, `key`)
- }
- }
- function createCodegenNodeForBranch(
- branch: IfBranchNode,
- keyIndex: number,
- context: TransformContext
- ): IfConditionalExpression | BlockCodegenNode {
- if (branch.condition) {
- return createConditionalExpression(
- branch.condition,
- createChildrenCodegenNode(branch, keyIndex, context),
- // 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'
- ])
- ) as IfConditionalExpression
- } else {
- return createChildrenCodegenNode(branch, keyIndex, context)
- }
- }
- function createChildrenCodegenNode(
- branch: IfBranchNode,
- keyIndex: number,
- context: TransformContext
- ): BlockCodegenNode {
- const { helper } = context
- const keyProperty = createObjectProperty(
- `key`,
- createSimpleExpression(
- `${keyIndex}`,
- false,
- locStub,
- ConstantTypes.CAN_HOIST
- )
- )
- const { children } = branch
- const firstChild = children[0]
- const needFragmentWrapper =
- children.length !== 1 || firstChild.type !== NodeTypes.ELEMENT
- if (needFragmentWrapper) {
- if (children.length === 1 && firstChild.type === NodeTypes.FOR) {
- // optimize away nested fragments when child is a ForNode
- const vnodeCall = firstChild.codegenNode!
- injectProp(vnodeCall, keyProperty, context)
- return vnodeCall
- } else {
- return createVNodeCall(
- context,
- helper(FRAGMENT),
- createObjectExpression([keyProperty]),
- children,
- PatchFlags.STABLE_FRAGMENT +
- (__DEV__
- ? ` /* ${PatchFlagNames[PatchFlags.STABLE_FRAGMENT]} */`
- : ``),
- undefined,
- undefined,
- true,
- false,
- branch.loc
- )
- }
- } else {
- const vnodeCall = (firstChild as ElementNode)
- .codegenNode as BlockCodegenNode
- // Change createVNode to createBlock.
- if (vnodeCall.type === NodeTypes.VNODE_CALL) {
- vnodeCall.isBlock = true
- helper(OPEN_BLOCK)
- helper(CREATE_BLOCK)
- }
- // inject branch key
- injectProp(vnodeCall, keyProperty, context)
- return vnodeCall
- }
- }
- 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
- }
- 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
- }
- }
- }
|