codegen.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. /* @flow */
  2. // The SSR codegen is essentially extending the default codegen to handle
  3. // SSR-optimizable nodes and turn them into string render fns. In cases where
  4. // a node is not optimizable it simply falls back to the default codegen.
  5. // import * as directives from './directives'
  6. import { optimizability } from './optimizer'
  7. import {
  8. genIf,
  9. genFor,
  10. genData,
  11. genText,
  12. genElement,
  13. genChildren,
  14. CodegenState
  15. } from 'compiler/codegen/index'
  16. import type { CodegenResult } from 'compiler/codegen/index'
  17. // segment types
  18. const RAW = 0
  19. const INTERPOLATION = 1
  20. const FLOW_CONTROL = 2
  21. type StringSegment = {
  22. type: number;
  23. value: string;
  24. };
  25. export function generate (
  26. ast: ASTElement | void,
  27. options: CompilerOptions
  28. ): CodegenResult {
  29. const state = new CodegenState(options)
  30. const code = ast ? genSSRElement(ast, state) : '_c("div")'
  31. return {
  32. render: `with(this){return ${code}}`,
  33. staticRenderFns: state.staticRenderFns
  34. }
  35. }
  36. function genSSRElement (el: ASTElement, state: CodegenState): string {
  37. if (el.for && !el.forProcessed) {
  38. return genFor(el, state, genSSRElement)
  39. } else if (el.if && !el.ifProcessed) {
  40. return genIf(el, state, genSSRElement)
  41. }
  42. switch (el.ssrOptimizability) {
  43. case optimizability.FULL:
  44. // stringify whole tree
  45. return genStringElement(el, state, true)
  46. case optimizability.SELF:
  47. // stringify self and check children
  48. return genStringElement(el, state, false)
  49. case optimizability.CHILDREN:
  50. // generate self as VNode and stringify children
  51. return genNormalElement(el, state, true)
  52. case optimizability.PARTIAL:
  53. // generate self as VNode and check children
  54. return genNormalElement(el, state, false)
  55. default:
  56. // bail whole tree
  57. return genElement(el, state)
  58. }
  59. }
  60. function genNormalElement (el, state, stringifyChildren) {
  61. const data = el.plain ? undefined : genData(el, state)
  62. const children = stringifyChildren
  63. ? genStringChildren(el, state)
  64. : genSSRChildren(el, state, true)
  65. return `_c('${el.tag}'${
  66. data ? `,${data}` : ''
  67. }${
  68. children ? `,${children}` : ''
  69. })`
  70. }
  71. function genSSRChildren (el, state, checkSkip) {
  72. return genChildren(el, state, checkSkip, genSSRElement, genSSRNode)
  73. }
  74. function genSSRNode (el, state) {
  75. return el.type === 1
  76. ? genSSRElement(el, state)
  77. : genText(el, state)
  78. }
  79. function genStringChildren (el, state) {
  80. return `[_ssrNode(${flattenSegments(childrenToSegments(el, state))})]`
  81. }
  82. function genStringElement (el, state, stringifyChildren) {
  83. if (stringifyChildren) {
  84. return `_ssrNode(${flattenSegments(elementToSegments(el, state))})`
  85. } else {
  86. const children = genSSRChildren(el, state, true)
  87. return `_ssrNode(${
  88. flattenSegments(elementToOpenTagSegments(el, state))
  89. }","${el.tag}"${
  90. children ? `,${children}` : ''
  91. })`
  92. }
  93. }
  94. function elementToSegments (el, state): Array<StringSegment> {
  95. if (el.for && !el.forProcessed) {
  96. el.forProcessed = true
  97. return [{
  98. type: FLOW_CONTROL,
  99. value: genFor(el, state, elementToString, '_ssrList')
  100. }]
  101. } else if (el.if && !el.ifProcessed) {
  102. el.ifProcessed = true
  103. return [{
  104. type: FLOW_CONTROL,
  105. value: genIf(el, state, elementToString, '""')
  106. }]
  107. }
  108. const openSegments = elementToOpenTagSegments(el, state)
  109. const childrenSegments = childrenToSegments(el, state)
  110. const { isUnaryTag } = state.options
  111. const close = (isUnaryTag && isUnaryTag(el.tag))
  112. ? []
  113. : [{ type: RAW, value: `</${el.tag}>` }]
  114. return openSegments.concat(childrenSegments, close)
  115. }
  116. function elementToString (el, state) {
  117. return flattenSegments(elementToSegments(el, state))
  118. }
  119. function elementToOpenTagSegments (el, state): Array<StringSegment> {
  120. // TODO: handle v-show, v-html & v-text
  121. // TODO: handle attrs/props/styles/classes
  122. return [{ type: RAW, value: `<${el.tag}>` }]
  123. }
  124. function childrenToSegments (el, state): Array<StringSegment> {
  125. const children = el.children
  126. if (children) {
  127. const segments = []
  128. for (let i = 0; i < children.length; i++) {
  129. const c = children[i]
  130. if (c.type === 1) {
  131. segments.push.apply(segments, elementToSegments(c, state))
  132. } else if (c.type === 2) {
  133. segments.push({ type: INTERPOLATION, value: c.expression })
  134. } else if (c.type === 3) {
  135. segments.push({ type: RAW, value: c.text })
  136. }
  137. }
  138. return segments
  139. } else {
  140. return []
  141. }
  142. }
  143. function flattenSegments (segments: Array<StringSegment>): string {
  144. const mergedSegments = []
  145. let textBuffer = ''
  146. const pushBuffer = () => {
  147. if (textBuffer) {
  148. mergedSegments.push(JSON.stringify(textBuffer))
  149. textBuffer = ''
  150. }
  151. }
  152. for (let i = 0; i < segments.length; i++) {
  153. const s = segments[i]
  154. if (s.type === RAW) {
  155. textBuffer += s.value
  156. } else if (s.type === INTERPOLATION) {
  157. pushBuffer()
  158. mergedSegments.push(`_ssrEscape(${s.value})`)
  159. } else if (s.type === FLOW_CONTROL) {
  160. pushBuffer()
  161. mergedSegments.push(`(${s.value})`)
  162. }
  163. }
  164. pushBuffer()
  165. return mergedSegments.join('+')
  166. }