vIf.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import {
  2. createStructuralDirectiveTransform,
  3. traverseChildren,
  4. TransformContext
  5. } from '../transform'
  6. import {
  7. NodeTypes,
  8. ElementTypes,
  9. ElementNode,
  10. DirectiveNode,
  11. IfBranchNode,
  12. SimpleExpressionNode,
  13. createSequenceExpression,
  14. createCallExpression,
  15. createConditionalExpression,
  16. ConditionalExpression,
  17. CallExpression,
  18. createSimpleExpression,
  19. createObjectProperty,
  20. createObjectExpression,
  21. IfCodegenNode,
  22. IfConditionalExpression,
  23. BlockCodegenNode,
  24. SlotOutletCodegenNode,
  25. ElementCodegenNode,
  26. ComponentCodegenNode
  27. } from '../ast'
  28. import { createCompilerError, ErrorCodes } from '../errors'
  29. import { processExpression } from './transformExpression'
  30. import {
  31. OPEN_BLOCK,
  32. CREATE_BLOCK,
  33. COMMENT,
  34. FRAGMENT,
  35. WITH_DIRECTIVES,
  36. CREATE_VNODE
  37. } from '../runtimeHelpers'
  38. import { injectProp } from '../utils'
  39. export const transformIf = createStructuralDirectiveTransform(
  40. /^(if|else|else-if)$/,
  41. (node, dir, context) => {
  42. if (
  43. dir.name !== 'else' &&
  44. (!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
  45. ) {
  46. const loc = dir.exp ? dir.exp.loc : node.loc
  47. context.onError(
  48. createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc)
  49. )
  50. dir.exp = createSimpleExpression(`true`, false, loc)
  51. }
  52. if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) {
  53. // dir.exp can only be simple expression because vIf transform is applied
  54. // before expression transform.
  55. dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
  56. }
  57. if (dir.name === 'if') {
  58. const branch = createIfBranch(node, dir)
  59. const codegenNode = createSequenceExpression([
  60. createCallExpression(context.helper(OPEN_BLOCK))
  61. ]) as IfCodegenNode
  62. context.replaceNode({
  63. type: NodeTypes.IF,
  64. loc: node.loc,
  65. branches: [branch],
  66. codegenNode
  67. })
  68. // Exit callback. Complete the codegenNode when all children have been
  69. // transformed.
  70. return () => {
  71. codegenNode.expressions.push(createCodegenNodeForBranch(
  72. branch,
  73. 0,
  74. context
  75. ) as IfConditionalExpression)
  76. }
  77. } else {
  78. // locate the adjacent v-if
  79. const siblings = context.parent!.children
  80. const comments = []
  81. let i = siblings.indexOf(node)
  82. while (i-- >= -1) {
  83. const sibling = siblings[i]
  84. if (__DEV__ && sibling && sibling.type === NodeTypes.COMMENT) {
  85. context.removeNode(sibling)
  86. comments.unshift(sibling)
  87. continue
  88. }
  89. if (sibling && sibling.type === NodeTypes.IF) {
  90. // move the node to the if node's branches
  91. context.removeNode()
  92. const branch = createIfBranch(node, dir)
  93. if (__DEV__ && comments.length) {
  94. branch.children = [...comments, ...branch.children]
  95. }
  96. sibling.branches.push(branch)
  97. // since the branch was removed, it will not be traversed.
  98. // make sure to traverse here.
  99. traverseChildren(branch, context)
  100. // make sure to reset currentNode after traversal to indicate this
  101. // node has been removed.
  102. context.currentNode = null
  103. // attach this branch's codegen node to the v-if root.
  104. let parentCondition = sibling.codegenNode
  105. .expressions[1] as ConditionalExpression
  106. while (true) {
  107. if (
  108. parentCondition.alternate.type ===
  109. NodeTypes.JS_CONDITIONAL_EXPRESSION
  110. ) {
  111. parentCondition = parentCondition.alternate
  112. } else {
  113. parentCondition.alternate = createCodegenNodeForBranch(
  114. branch,
  115. sibling.branches.length - 1,
  116. context
  117. )
  118. break
  119. }
  120. }
  121. } else {
  122. context.onError(
  123. createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
  124. )
  125. }
  126. break
  127. }
  128. }
  129. }
  130. )
  131. function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
  132. return {
  133. type: NodeTypes.IF_BRANCH,
  134. loc: node.loc,
  135. condition: dir.name === 'else' ? undefined : dir.exp,
  136. children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node]
  137. }
  138. }
  139. function createCodegenNodeForBranch(
  140. branch: IfBranchNode,
  141. index: number,
  142. context: TransformContext
  143. ): IfConditionalExpression | BlockCodegenNode {
  144. if (branch.condition) {
  145. return createConditionalExpression(
  146. branch.condition,
  147. createChildrenCodegenNode(branch, index, context),
  148. createCallExpression(context.helper(CREATE_BLOCK), [
  149. context.helper(COMMENT)
  150. ])
  151. ) as IfConditionalExpression
  152. } else {
  153. return createChildrenCodegenNode(branch, index, context) as BlockCodegenNode
  154. }
  155. }
  156. function createChildrenCodegenNode(
  157. branch: IfBranchNode,
  158. index: number,
  159. context: TransformContext
  160. ): CallExpression {
  161. const { helper } = context
  162. const keyProperty = createObjectProperty(
  163. `key`,
  164. createSimpleExpression(index + '', false)
  165. )
  166. const { children } = branch
  167. const child = children[0]
  168. const needFragmentWrapper =
  169. children.length !== 1 || child.type !== NodeTypes.ELEMENT
  170. if (needFragmentWrapper) {
  171. const blockArgs: CallExpression['arguments'] = [
  172. helper(FRAGMENT),
  173. createObjectExpression([keyProperty]),
  174. children
  175. ]
  176. if (children.length === 1 && child.type === NodeTypes.FOR) {
  177. // optimize away nested fragments when child is a ForNode
  178. const forBlockArgs = child.codegenNode.expressions[1].arguments
  179. // directly use the for block's children and patchFlag
  180. blockArgs[2] = forBlockArgs[2]
  181. blockArgs[3] = forBlockArgs[3]
  182. }
  183. return createCallExpression(helper(CREATE_BLOCK), blockArgs)
  184. } else {
  185. const childCodegen = (child as ElementNode).codegenNode as
  186. | ElementCodegenNode
  187. | ComponentCodegenNode
  188. | SlotOutletCodegenNode
  189. let vnodeCall = childCodegen
  190. // Element with custom directives. Locate the actual createVNode() call.
  191. if (vnodeCall.callee === WITH_DIRECTIVES) {
  192. vnodeCall = vnodeCall.arguments[0]
  193. }
  194. // Change createVNode to createBlock.
  195. if (vnodeCall.callee === CREATE_VNODE) {
  196. vnodeCall.callee = helper(CREATE_BLOCK)
  197. }
  198. // inject branch key
  199. injectProp(vnodeCall, keyProperty, context)
  200. return childCodegen
  201. }
  202. }