vIf.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import {
  2. type ElementNode,
  3. ErrorCodes,
  4. NodeTypes,
  5. type TemplateChildNode,
  6. createCompilerError,
  7. createSimpleExpression,
  8. } from '@vue/compiler-dom'
  9. import {
  10. type TransformContext,
  11. createStructuralDirectiveTransform,
  12. genDefaultDynamic,
  13. wrapTemplate,
  14. } from '../transform'
  15. import {
  16. type BlockFunctionIRNode,
  17. DynamicFlag,
  18. type IRDynamicInfo,
  19. IRNodeTypes,
  20. type OperationNode,
  21. type VaporDirectiveNode,
  22. } from '../ir'
  23. import { extend } from '@vue/shared'
  24. export const transformVIf = createStructuralDirectiveTransform(
  25. ['if', 'else', 'else-if'],
  26. processIf,
  27. )
  28. export function processIf(
  29. node: ElementNode,
  30. dir: VaporDirectiveNode,
  31. context: TransformContext<ElementNode>,
  32. ) {
  33. if (dir.name !== 'else' && (!dir.exp || !dir.exp.content.trim())) {
  34. const loc = dir.exp ? dir.exp.loc : node.loc
  35. context.options.onError(
  36. createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc),
  37. )
  38. dir.exp = createSimpleExpression(`true`, false, loc)
  39. }
  40. context.dynamic.flags |= DynamicFlag.NON_TEMPLATE
  41. if (dir.name === 'if') {
  42. const id = context.reference()
  43. context.dynamic.flags |= DynamicFlag.INSERT
  44. const [branch, onExit] = createIfBranch(node, context)
  45. return () => {
  46. onExit()
  47. context.registerOperation({
  48. type: IRNodeTypes.IF,
  49. id,
  50. condition: dir.exp!,
  51. positive: branch,
  52. })
  53. }
  54. } else {
  55. // check the adjacent v-if
  56. const parent = context.parent!
  57. const siblings = parent.node.children
  58. const templates = parent.childrenTemplate
  59. const siblingsDynamic = parent.dynamic.children
  60. const comments = []
  61. let sibling: TemplateChildNode | undefined
  62. let i = siblings.indexOf(node)
  63. while (i-- >= -1) {
  64. sibling = siblings[i]
  65. if (
  66. sibling &&
  67. (sibling.type === NodeTypes.COMMENT ||
  68. (sibling.type === NodeTypes.TEXT && !sibling.content.trim().length))
  69. ) {
  70. if (__DEV__ && sibling.type === NodeTypes.COMMENT)
  71. comments.unshift(sibling)
  72. siblingsDynamic[i].flags |= DynamicFlag.NON_TEMPLATE
  73. templates[i] = null
  74. } else {
  75. break
  76. }
  77. }
  78. const { operation } = context.block
  79. let lastIfNode: OperationNode
  80. if (
  81. // check if v-if is the sibling node
  82. !sibling ||
  83. sibling.type !== NodeTypes.ELEMENT ||
  84. !sibling.props.some(
  85. ({ type, name }) =>
  86. type === NodeTypes.DIRECTIVE && ['if', 'else-if'].includes(name),
  87. ) ||
  88. // check if IFNode is the last operation and get the root IFNode
  89. !(lastIfNode = operation[operation.length - 1]) ||
  90. lastIfNode.type !== IRNodeTypes.IF
  91. ) {
  92. context.options.onError(
  93. createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc),
  94. )
  95. return
  96. }
  97. while (lastIfNode.negative && lastIfNode.negative.type === IRNodeTypes.IF) {
  98. lastIfNode = lastIfNode.negative
  99. }
  100. // Check if v-else was followed by v-else-if
  101. if (dir.name === 'else-if' && lastIfNode.negative) {
  102. context.options.onError(
  103. createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc),
  104. )
  105. }
  106. // TODO ignore comments if the v-if is direct child of <transition> (PR #3622)
  107. if (__DEV__ && comments.length) {
  108. node = wrapTemplate(node, ['else-if', 'else'])
  109. context.node = node = extend({}, node, {
  110. children: [...comments, ...node.children],
  111. })
  112. }
  113. const [branch, onExit] = createIfBranch(node, context)
  114. if (dir.name === 'else') {
  115. lastIfNode.negative = branch
  116. } else {
  117. lastIfNode.negative = {
  118. type: IRNodeTypes.IF,
  119. id: -1,
  120. condition: dir.exp!,
  121. positive: branch,
  122. }
  123. }
  124. return () => onExit()
  125. }
  126. }
  127. export function createIfBranch(
  128. node: ElementNode,
  129. context: TransformContext<ElementNode>,
  130. ): [BlockFunctionIRNode, () => void] {
  131. context.node = node = wrapTemplate(node, ['if', 'else-if', 'else'])
  132. const branch: BlockFunctionIRNode = {
  133. type: IRNodeTypes.BLOCK_FUNCTION,
  134. node,
  135. templateIndex: -1,
  136. dynamic: extend(genDefaultDynamic(), {
  137. flags: DynamicFlag.REFERENCED,
  138. } satisfies Partial<IRDynamicInfo>),
  139. effect: [],
  140. operation: [],
  141. }
  142. const exitBlock = context.enterBlock(branch)
  143. context.reference()
  144. const onExit = () => {
  145. context.template += context.childrenTemplate.filter(Boolean).join('')
  146. context.registerTemplate()
  147. exitBlock()
  148. }
  149. return [branch, onExit]
  150. }