ssrCodegenTransform.ts 7.3 KB

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