vIf.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. import {
  2. createStructuralDirectiveTransform,
  3. TransformContext,
  4. traverseNode
  5. } from '../transform'
  6. import {
  7. NodeTypes,
  8. ElementTypes,
  9. ElementNode,
  10. DirectiveNode,
  11. IfBranchNode,
  12. SimpleExpressionNode,
  13. createCallExpression,
  14. createConditionalExpression,
  15. createSimpleExpression,
  16. createObjectProperty,
  17. createObjectExpression,
  18. IfConditionalExpression,
  19. BlockCodegenNode,
  20. IfNode,
  21. createVNodeCall,
  22. AttributeNode,
  23. locStub,
  24. CacheExpression,
  25. ConstantTypes
  26. } from '../ast'
  27. import { createCompilerError, ErrorCodes } from '../errors'
  28. import { processExpression } from './transformExpression'
  29. import { validateBrowserExpression } from '../validateExpression'
  30. import {
  31. CREATE_BLOCK,
  32. FRAGMENT,
  33. CREATE_COMMENT,
  34. OPEN_BLOCK
  35. } from '../runtimeHelpers'
  36. import { injectProp, findDir, findProp } from '../utils'
  37. import { PatchFlags, PatchFlagNames } from '@vue/shared'
  38. export const transformIf = createStructuralDirectiveTransform(
  39. /^(if|else|else-if)$/,
  40. (node, dir, context) => {
  41. return processIf(node, dir, context, (ifNode, branch, isRoot) => {
  42. // #1587: We need to dynamically increment the key based on the current
  43. // node's sibling nodes, since chained v-if/else branches are
  44. // rendered at the same depth
  45. const siblings = context.parent!.children
  46. let i = siblings.indexOf(ifNode)
  47. let key = 0
  48. while (i-- >= 0) {
  49. const sibling = siblings[i]
  50. if (sibling && sibling.type === NodeTypes.IF) {
  51. key += sibling.branches.length
  52. }
  53. }
  54. // Exit callback. Complete the codegenNode when all children have been
  55. // transformed.
  56. return () => {
  57. if (isRoot) {
  58. ifNode.codegenNode = createCodegenNodeForBranch(
  59. branch,
  60. key,
  61. context
  62. ) as IfConditionalExpression
  63. } else {
  64. // attach this branch's codegen node to the v-if root.
  65. const parentCondition = getParentCondition(ifNode.codegenNode!)
  66. parentCondition.alternate = createCodegenNodeForBranch(
  67. branch,
  68. key + ifNode.branches.length - 1,
  69. context
  70. )
  71. }
  72. }
  73. })
  74. }
  75. )
  76. // target-agnostic transform used for both Client and SSR
  77. export function processIf(
  78. node: ElementNode,
  79. dir: DirectiveNode,
  80. context: TransformContext,
  81. processCodegen?: (
  82. node: IfNode,
  83. branch: IfBranchNode,
  84. isRoot: boolean
  85. ) => (() => void) | undefined
  86. ) {
  87. if (
  88. dir.name !== 'else' &&
  89. (!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
  90. ) {
  91. const loc = dir.exp ? dir.exp.loc : node.loc
  92. context.onError(
  93. createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc)
  94. )
  95. dir.exp = createSimpleExpression(`true`, false, loc)
  96. }
  97. if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) {
  98. // dir.exp can only be simple expression because vIf transform is applied
  99. // before expression transform.
  100. dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
  101. }
  102. if (__DEV__ && __BROWSER__ && dir.exp) {
  103. validateBrowserExpression(dir.exp as SimpleExpressionNode, context)
  104. }
  105. if (dir.name === 'if') {
  106. const branch = createIfBranch(node, dir)
  107. const ifNode: IfNode = {
  108. type: NodeTypes.IF,
  109. loc: node.loc,
  110. branches: [branch]
  111. }
  112. context.replaceNode(ifNode)
  113. if (processCodegen) {
  114. return processCodegen(ifNode, branch, true)
  115. }
  116. } else {
  117. // locate the adjacent v-if
  118. const siblings = context.parent!.children
  119. const comments = []
  120. let i = siblings.indexOf(node)
  121. while (i-- >= -1) {
  122. const sibling = siblings[i]
  123. if (__DEV__ && sibling && sibling.type === NodeTypes.COMMENT) {
  124. context.removeNode(sibling)
  125. comments.unshift(sibling)
  126. continue
  127. }
  128. if (
  129. sibling &&
  130. sibling.type === NodeTypes.TEXT &&
  131. !sibling.content.trim().length
  132. ) {
  133. context.removeNode(sibling)
  134. continue
  135. }
  136. if (sibling && sibling.type === NodeTypes.IF) {
  137. // move the node to the if node's branches
  138. context.removeNode()
  139. const branch = createIfBranch(node, dir)
  140. if (__DEV__ && comments.length) {
  141. branch.children = [...comments, ...branch.children]
  142. }
  143. // check if user is forcing same key on different branches
  144. if (__DEV__ || !__BROWSER__) {
  145. const key = branch.userKey
  146. if (key) {
  147. sibling.branches.forEach(({ userKey }) => {
  148. if (isSameKey(userKey, key)) {
  149. context.onError(
  150. createCompilerError(
  151. ErrorCodes.X_V_IF_SAME_KEY,
  152. branch.userKey!.loc
  153. )
  154. )
  155. }
  156. })
  157. }
  158. }
  159. sibling.branches.push(branch)
  160. const onExit = processCodegen && processCodegen(sibling, branch, false)
  161. // since the branch was removed, it will not be traversed.
  162. // make sure to traverse here.
  163. traverseNode(branch, context)
  164. // call on exit
  165. if (onExit) onExit()
  166. // make sure to reset currentNode after traversal to indicate this
  167. // node has been removed.
  168. context.currentNode = null
  169. } else {
  170. context.onError(
  171. createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
  172. )
  173. }
  174. break
  175. }
  176. }
  177. }
  178. function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
  179. return {
  180. type: NodeTypes.IF_BRANCH,
  181. loc: node.loc,
  182. condition: dir.name === 'else' ? undefined : dir.exp,
  183. children:
  184. node.tagType === ElementTypes.TEMPLATE && !findDir(node, 'for')
  185. ? node.children
  186. : [node],
  187. userKey: findProp(node, `key`)
  188. }
  189. }
  190. function createCodegenNodeForBranch(
  191. branch: IfBranchNode,
  192. keyIndex: number,
  193. context: TransformContext
  194. ): IfConditionalExpression | BlockCodegenNode {
  195. if (branch.condition) {
  196. return createConditionalExpression(
  197. branch.condition,
  198. createChildrenCodegenNode(branch, keyIndex, context),
  199. // make sure to pass in asBlock: true so that the comment node call
  200. // closes the current block.
  201. createCallExpression(context.helper(CREATE_COMMENT), [
  202. __DEV__ ? '"v-if"' : '""',
  203. 'true'
  204. ])
  205. ) as IfConditionalExpression
  206. } else {
  207. return createChildrenCodegenNode(branch, keyIndex, context)
  208. }
  209. }
  210. function createChildrenCodegenNode(
  211. branch: IfBranchNode,
  212. keyIndex: number,
  213. context: TransformContext
  214. ): BlockCodegenNode {
  215. const { helper } = context
  216. const keyProperty = createObjectProperty(
  217. `key`,
  218. createSimpleExpression(
  219. `${keyIndex}`,
  220. false,
  221. locStub,
  222. ConstantTypes.CAN_HOIST
  223. )
  224. )
  225. const { children } = branch
  226. const firstChild = children[0]
  227. const needFragmentWrapper =
  228. children.length !== 1 || firstChild.type !== NodeTypes.ELEMENT
  229. if (needFragmentWrapper) {
  230. if (children.length === 1 && firstChild.type === NodeTypes.FOR) {
  231. // optimize away nested fragments when child is a ForNode
  232. const vnodeCall = firstChild.codegenNode!
  233. injectProp(vnodeCall, keyProperty, context)
  234. return vnodeCall
  235. } else {
  236. return createVNodeCall(
  237. context,
  238. helper(FRAGMENT),
  239. createObjectExpression([keyProperty]),
  240. children,
  241. PatchFlags.STABLE_FRAGMENT +
  242. (__DEV__
  243. ? ` /* ${PatchFlagNames[PatchFlags.STABLE_FRAGMENT]} */`
  244. : ``),
  245. undefined,
  246. undefined,
  247. true,
  248. false,
  249. branch.loc
  250. )
  251. }
  252. } else {
  253. const vnodeCall = (firstChild as ElementNode)
  254. .codegenNode as BlockCodegenNode
  255. // Change createVNode to createBlock.
  256. if (vnodeCall.type === NodeTypes.VNODE_CALL) {
  257. vnodeCall.isBlock = true
  258. helper(OPEN_BLOCK)
  259. helper(CREATE_BLOCK)
  260. }
  261. // inject branch key
  262. injectProp(vnodeCall, keyProperty, context)
  263. return vnodeCall
  264. }
  265. }
  266. function isSameKey(
  267. a: AttributeNode | DirectiveNode | undefined,
  268. b: AttributeNode | DirectiveNode
  269. ): boolean {
  270. if (!a || a.type !== b.type) {
  271. return false
  272. }
  273. if (a.type === NodeTypes.ATTRIBUTE) {
  274. if (a.value!.content !== (b as AttributeNode).value!.content) {
  275. return false
  276. }
  277. } else {
  278. // directive
  279. const exp = a.exp!
  280. const branchExp = (b as DirectiveNode).exp!
  281. if (exp.type !== branchExp.type) {
  282. return false
  283. }
  284. if (
  285. exp.type !== NodeTypes.SIMPLE_EXPRESSION ||
  286. (exp.isStatic !== (branchExp as SimpleExpressionNode).isStatic ||
  287. exp.content !== (branchExp as SimpleExpressionNode).content)
  288. ) {
  289. return false
  290. }
  291. }
  292. return true
  293. }
  294. function getParentCondition(
  295. node: IfConditionalExpression | CacheExpression
  296. ): IfConditionalExpression {
  297. while (true) {
  298. if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {
  299. if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {
  300. node = node.alternate
  301. } else {
  302. return node
  303. }
  304. } else if (node.type === NodeTypes.JS_CACHE_EXPRESSION) {
  305. node = node.value as IfConditionalExpression
  306. }
  307. }
  308. }