ssrCodegenTransform.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import {
  2. RootNode,
  3. BlockStatement,
  4. TemplateLiteral,
  5. createCallExpression,
  6. createTemplateLiteral,
  7. NodeTypes,
  8. TemplateChildNode,
  9. ElementTypes,
  10. createBlockStatement,
  11. CompilerOptions,
  12. IfStatement,
  13. CallExpression,
  14. isText,
  15. processExpression,
  16. createSimpleExpression,
  17. createCompoundExpression,
  18. createTransformContext,
  19. createRoot
  20. } from '@vue/compiler-dom'
  21. import { isString, escapeHtml } from '@vue/shared'
  22. import { SSR_INTERPOLATE, ssrHelpers } from './runtimeHelpers'
  23. import { ssrProcessIf } from './transforms/ssrVIf'
  24. import { ssrProcessFor } from './transforms/ssrVFor'
  25. import { ssrProcessSlotOutlet } from './transforms/ssrTransformSlotOutlet'
  26. import { ssrProcessComponent } from './transforms/ssrTransformComponent'
  27. import { ssrProcessElement } from './transforms/ssrTransformElement'
  28. import { createSSRCompilerError, SSRErrorCodes } from './errors'
  29. // Because SSR codegen output is completely different from client-side output
  30. // (e.g. multiple elements can be concatenated into a single template literal
  31. // instead of each getting a corresponding call), we need to apply an extra
  32. // transform pass to convert the template AST into a fresh JS AST before
  33. // passing it to codegen.
  34. export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
  35. const context = createSSRTransformContext(ast, options)
  36. // inject SFC <style> CSS variables
  37. // we do this instead of inlining the expression to ensure the vars are
  38. // only resolved once per render
  39. if (options.ssrCssVars) {
  40. const cssContext = createTransformContext(createRoot([]), options)
  41. const varsExp = processExpression(
  42. createSimpleExpression(options.ssrCssVars, false),
  43. cssContext
  44. )
  45. context.body.push(
  46. createCompoundExpression([`const _cssVars = { style: `, varsExp, `}`])
  47. )
  48. Array.from(cssContext.helpers.keys()).forEach(helper => {
  49. ast.helpers.add(helper)
  50. })
  51. }
  52. const isFragment =
  53. ast.children.length > 1 && ast.children.some(c => !isText(c))
  54. processChildren(ast, context, isFragment)
  55. ast.codegenNode = createBlockStatement(context.body)
  56. // Finalize helpers.
  57. // We need to separate helpers imported from 'vue' vs. '@vue/server-renderer'
  58. ast.ssrHelpers = Array.from(
  59. new Set([
  60. ...Array.from(ast.helpers).filter(h => h in ssrHelpers),
  61. ...context.helpers
  62. ])
  63. )
  64. ast.helpers = new Set(Array.from(ast.helpers).filter(h => !(h in ssrHelpers)))
  65. }
  66. export type SSRTransformContext = ReturnType<typeof createSSRTransformContext>
  67. function createSSRTransformContext(
  68. root: RootNode,
  69. options: CompilerOptions,
  70. helpers: Set<symbol> = new Set(),
  71. withSlotScopeId = false
  72. ) {
  73. const body: BlockStatement['body'] = []
  74. let currentString: TemplateLiteral | null = null
  75. return {
  76. root,
  77. options,
  78. body,
  79. helpers,
  80. withSlotScopeId,
  81. onError:
  82. options.onError ||
  83. (e => {
  84. throw e
  85. }),
  86. helper<T extends symbol>(name: T): T {
  87. helpers.add(name)
  88. return name
  89. },
  90. pushStringPart(part: TemplateLiteral['elements'][0]) {
  91. if (!currentString) {
  92. const currentCall = createCallExpression(`_push`)
  93. body.push(currentCall)
  94. currentString = createTemplateLiteral([])
  95. currentCall.arguments.push(currentString)
  96. }
  97. const bufferedElements = currentString.elements
  98. const lastItem = bufferedElements[bufferedElements.length - 1]
  99. if (isString(part) && isString(lastItem)) {
  100. bufferedElements[bufferedElements.length - 1] += part
  101. } else {
  102. bufferedElements.push(part)
  103. }
  104. },
  105. pushStatement(statement: IfStatement | CallExpression) {
  106. // close current string
  107. currentString = null
  108. body.push(statement)
  109. }
  110. }
  111. }
  112. function createChildContext(
  113. parent: SSRTransformContext,
  114. withSlotScopeId = parent.withSlotScopeId
  115. ): SSRTransformContext {
  116. // ensure child inherits parent helpers
  117. return createSSRTransformContext(
  118. parent.root,
  119. parent.options,
  120. parent.helpers,
  121. withSlotScopeId
  122. )
  123. }
  124. interface Container {
  125. children: TemplateChildNode[]
  126. }
  127. export function processChildren(
  128. parent: Container,
  129. context: SSRTransformContext,
  130. asFragment = false,
  131. disableNestedFragments = false
  132. ) {
  133. if (asFragment) {
  134. context.pushStringPart(`<!--[-->`)
  135. }
  136. const { children } = parent
  137. for (let i = 0; i < children.length; i++) {
  138. const child = children[i]
  139. switch (child.type) {
  140. case NodeTypes.ELEMENT:
  141. switch (child.tagType) {
  142. case ElementTypes.ELEMENT:
  143. ssrProcessElement(child, context)
  144. break
  145. case ElementTypes.COMPONENT:
  146. ssrProcessComponent(child, context, parent)
  147. break
  148. case ElementTypes.SLOT:
  149. ssrProcessSlotOutlet(child, context)
  150. break
  151. case ElementTypes.TEMPLATE:
  152. // TODO
  153. break
  154. default:
  155. context.onError(
  156. createSSRCompilerError(
  157. SSRErrorCodes.X_SSR_INVALID_AST_NODE,
  158. (child as any).loc
  159. )
  160. )
  161. // make sure we exhaust all possible types
  162. const exhaustiveCheck: never = child
  163. return exhaustiveCheck
  164. }
  165. break
  166. case NodeTypes.TEXT:
  167. context.pushStringPart(escapeHtml(child.content))
  168. break
  169. case NodeTypes.COMMENT:
  170. // no need to escape comment here because the AST can only
  171. // contain valid comments.
  172. context.pushStringPart(`<!--${child.content}-->`)
  173. break
  174. case NodeTypes.INTERPOLATION:
  175. context.pushStringPart(
  176. createCallExpression(context.helper(SSR_INTERPOLATE), [child.content])
  177. )
  178. break
  179. case NodeTypes.IF:
  180. ssrProcessIf(child, context, disableNestedFragments)
  181. break
  182. case NodeTypes.FOR:
  183. ssrProcessFor(child, context, disableNestedFragments)
  184. break
  185. case NodeTypes.IF_BRANCH:
  186. // no-op - handled by ssrProcessIf
  187. break
  188. case NodeTypes.TEXT_CALL:
  189. case NodeTypes.COMPOUND_EXPRESSION:
  190. // no-op - these two types can never appear as template child node since
  191. // `transformText` is not used during SSR compile.
  192. break
  193. default:
  194. context.onError(
  195. createSSRCompilerError(
  196. SSRErrorCodes.X_SSR_INVALID_AST_NODE,
  197. (child as any).loc
  198. )
  199. )
  200. // make sure we exhaust all possible types
  201. const exhaustiveCheck: never = child
  202. return exhaustiveCheck
  203. }
  204. }
  205. if (asFragment) {
  206. context.pushStringPart(`<!--]-->`)
  207. }
  208. }
  209. export function processChildrenAsStatement(
  210. parent: Container,
  211. parentContext: SSRTransformContext,
  212. asFragment = false,
  213. withSlotScopeId = parentContext.withSlotScopeId
  214. ): BlockStatement {
  215. const childContext = createChildContext(parentContext, withSlotScopeId)
  216. processChildren(parent, childContext, asFragment)
  217. return createBlockStatement(childContext.body)
  218. }