f67bcfd6821c876a58891a24d1c6dd2e9b40b6e57785f0168858016554958b034018348c36066b2a48d3cce5933438406b507aaa63acb4c260ec355420bbd7 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  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 {
  6. genIf,
  7. genFor,
  8. genData,
  9. genText,
  10. genElement,
  11. genChildren,
  12. CodegenState
  13. } from 'compiler/codegen/index'
  14. import {
  15. genAttrSegments,
  16. genDOMPropSegments,
  17. genClassSegments,
  18. genStyleSegments,
  19. applyModelTransform
  20. } from './modules'
  21. import { escape } from 'web/server/util'
  22. import { optimizability } from './optimizer'
  23. import type { CodegenResult } from 'compiler/codegen/index'
  24. export type StringSegment = {
  25. type: number;
  26. value: string;
  27. };
  28. // segment types
  29. export const RAW = 0
  30. export const INTERPOLATION = 1
  31. export const EXPRESSION = 2
  32. export function generate (
  33. ast: ASTElement | void,
  34. options: CompilerOptions
  35. ): CodegenResult {
  36. const state = new CodegenState(options)
  37. const code = ast ? genSSRElement(ast, state) : '_c("div")'
  38. return {
  39. render: `with(this){return ${code}}`,
  40. staticRenderFns: state.staticRenderFns
  41. }
  42. }
  43. function genSSRElement (el: ASTElement, state: CodegenState): string {
  44. if (el.for && !el.forProcessed) {
  45. return genFor(el, state, genSSRElement)
  46. } else if (el.if && !el.ifProcessed) {
  47. return genIf(el, state, genSSRElement)
  48. } else if (el.tag === 'template' && !el.slotTarget) {
  49. return el.ssrOptimizability === optimizability.FULL
  50. ? genChildrenAsStringNode(el, state)
  51. : genSSRChildren(el, state) || 'void 0'
  52. }
  53. switch (el.ssrOptimizability) {
  54. case optimizability.FULL:
  55. // stringify whole tree
  56. return genStringElement(el, state)
  57. case optimizability.SELF:
  58. // stringify self and check children
  59. return genStringElementWithChildren(el, state)
  60. case optimizability.CHILDREN:
  61. // generate self as VNode and stringify children
  62. return genNormalElement(el, state, true)
  63. case optimizability.PARTIAL:
  64. // generate self as VNode and check children
  65. return genNormalElement(el, state, false)
  66. default:
  67. // bail whole tree
  68. return genElement(el, state)
  69. }
  70. }
  71. function genNormalElement (el, state, stringifyChildren) {
  72. const data = el.plain ? undefined : genData(el, state)
  73. const children = stringifyChildren
  74. ? `[${genChildrenAsStringNode(el, state)}]`
  75. : genSSRChildren(el, state, true)
  76. return `_c('${el.tag}'${
  77. data ? `,${data}` : ''
  78. }${
  79. children ? `,${children}` : ''
  80. })`
  81. }
  82. function genSSRChildren (el, state, checkSkip) {
  83. return genChildren(el, state, checkSkip, genSSRElement, genSSRNode)
  84. }
  85. function genSSRNode (el, state) {
  86. return el.type === 1
  87. ? genSSRElement(el, state)
  88. : genText(el)
  89. }
  90. function genChildrenAsStringNode (el, state) {
  91. return el.children.length
  92. ? `_ssrNode(${flattenSegments(childrenToSegments(el, state))})`
  93. : ''
  94. }
  95. function genStringElement (el, state) {
  96. return `_ssrNode(${elementToString(el, state)})`
  97. }
  98. function genStringElementWithChildren (el, state) {
  99. const children = genSSRChildren(el, state, true)
  100. return `_ssrNode(${
  101. flattenSegments(elementToOpenTagSegments(el, state))
  102. },"</${el.tag}>"${
  103. children ? `,${children}` : ''
  104. })`
  105. }
  106. function elementToString (el, state) {
  107. return `(${flattenSegments(elementToSegments(el, state))})`
  108. }
  109. function elementToSegments (el, state): Array<StringSegment> {
  110. // v-for / v-if
  111. if (el.for && !el.forProcessed) {
  112. el.forProcessed = true
  113. return [{
  114. type: EXPRESSION,
  115. value: genFor(el, state, elementToString, '_ssrList')
  116. }]
  117. } else if (el.if && !el.ifProcessed) {
  118. el.ifProcessed = true
  119. return [{
  120. type: EXPRESSION,
  121. value: genIf(el, state, elementToString, '"<!---->"')
  122. }]
  123. } else if (el.tag === 'template') {
  124. return childrenToSegments(el, state)
  125. }
  126. const openSegments = elementToOpenTagSegments(el, state)
  127. const childrenSegments = childrenToSegments(el, state)
  128. const { isUnaryTag } = state.options
  129. const close = (isUnaryTag && isUnaryTag(el.tag))
  130. ? []
  131. : [{ type: RAW, value: `</${el.tag}>` }]
  132. return openSegments.concat(childrenSegments, close)
  133. }
  134. function elementToOpenTagSegments (el, state): Array<StringSegment> {
  135. applyModelTransform(el, state)
  136. let binding
  137. const segments = [{ type: RAW, value: `<${el.tag}` }]
  138. // attrs
  139. if (el.attrs) {
  140. segments.push.apply(segments, genAttrSegments(el.attrs))
  141. }
  142. // domProps
  143. if (el.props) {
  144. segments.push.apply(segments, genDOMPropSegments(el.props, el.attrs))
  145. }
  146. // v-bind="object"
  147. if ((binding = el.attrsMap['v-bind'])) {
  148. segments.push({ type: EXPRESSION, value: `_ssrAttrs(${binding})` })
  149. }
  150. // v-bind.prop="object"
  151. if ((binding = el.attrsMap['v-bind.prop'])) {
  152. segments.push({ type: EXPRESSION, value: `_ssrDOMProps(${binding})` })
  153. }
  154. // class
  155. if (el.staticClass || el.classBinding) {
  156. segments.push.apply(
  157. segments,
  158. genClassSegments(el.staticClass, el.classBinding)
  159. )
  160. }
  161. // style & v-show
  162. if (el.staticStyle || el.styleBinding || el.attrsMap['v-show']) {
  163. segments.push.apply(
  164. segments,
  165. genStyleSegments(
  166. el.attrsMap.style,
  167. el.staticStyle,
  168. el.styleBinding,
  169. el.attrsMap['v-show']
  170. )
  171. )
  172. }
  173. // _scopedId
  174. if (state.options.scopeId) {
  175. segments.push({ type: RAW, value: ` ${state.options.scopeId}` })
  176. }
  177. segments.push({ type: RAW, value: `>` })
  178. return segments
  179. }
  180. function childrenToSegments (el, state): Array<StringSegment> {
  181. let binding
  182. if ((binding = el.attrsMap['v-html'])) {
  183. return [{ type: EXPRESSION, value: `_s(${binding})` }]
  184. }
  185. if ((binding = el.attrsMap['v-text'])) {
  186. return [{ type: INTERPOLATION, value: `_s(${binding})` }]
  187. }
  188. if (el.tag === 'textarea' && (binding = el.attrsMap['v-model'])) {
  189. return [{ type: INTERPOLATION, value: `_s(${binding})` }]
  190. }
  191. return el.children
  192. ? nodesToSegments(el.children, state)
  193. : []
  194. }
  195. function nodesToSegments (
  196. children: Array<ASTNode>,
  197. state: CodegenState
  198. ): Array<StringSegment> {
  199. const segments = []
  200. for (let i = 0; i < children.length; i++) {
  201. const c = children[i]
  202. if (c.type === 1) {
  203. segments.push.apply(segments, elementToSegments(c, state))
  204. } else if (c.type === 2) {
  205. segments.push({ type: INTERPOLATION, value: c.expression })
  206. } else if (c.type === 3) {
  207. let text = escape(c.text)
  208. if (c.isComment) {
  209. text = '<!--' + text + '-->'
  210. }
  211. segments.push({ type: RAW, value: text })
  212. }
  213. }
  214. return segments
  215. }
  216. function flattenSegments (segments: Array<StringSegment>): string {
  217. const mergedSegments = []
  218. let textBuffer = ''
  219. const pushBuffer = () => {
  220. if (textBuffer) {
  221. mergedSegments.push(JSON.stringify(textBuffer))
  222. textBuffer = ''
  223. }
  224. }
  225. for (let i = 0; i < segments.length; i++) {
  226. const s = segments[i]
  227. if (s.type === RAW) {
  228. textBuffer += s.value
  229. } else if (s.type === INTERPOLATION) {
  230. pushBuffer()
  231. mergedSegments.push(`_ssrEscape(${s.value})`)
  232. } else if (s.type === EXPRESSION) {
  233. pushBuffer()
  234. mergedSegments.push(`(${s.value})`)
  235. }
  236. }
  237. pushBuffer()
  238. return mergedSegments.join('+')
  239. }