ssrTransformComponent.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. import {
  2. NodeTransform,
  3. NodeTypes,
  4. ElementTypes,
  5. createCallExpression,
  6. resolveComponentType,
  7. buildProps,
  8. ComponentNode,
  9. SlotFnBuilder,
  10. createFunctionExpression,
  11. buildSlots,
  12. FunctionExpression,
  13. TemplateChildNode,
  14. TELEPORT,
  15. createIfStatement,
  16. createSimpleExpression,
  17. getBaseTransformPreset,
  18. DOMNodeTransforms,
  19. DOMDirectiveTransforms,
  20. createReturnStatement,
  21. ReturnStatement,
  22. Namespaces,
  23. locStub,
  24. RootNode,
  25. TransformContext,
  26. CompilerOptions,
  27. TransformOptions,
  28. createRoot,
  29. createTransformContext,
  30. traverseNode,
  31. ExpressionNode,
  32. TemplateNode,
  33. SUSPENSE,
  34. TRANSITION_GROUP
  35. } from '@vue/compiler-dom'
  36. import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
  37. import {
  38. SSRTransformContext,
  39. processChildren,
  40. processChildrenAsStatement
  41. } from '../ssrCodegenTransform'
  42. import { ssrProcessTeleport } from './ssrTransformTeleport'
  43. import {
  44. ssrProcessSuspense,
  45. ssrTransformSuspense
  46. } from './ssrTransformSuspense'
  47. import { isSymbol, isObject, isArray } from '@vue/shared'
  48. // We need to construct the slot functions in the 1st pass to ensure proper
  49. // scope tracking, but the children of each slot cannot be processed until
  50. // the 2nd pass, so we store the WIP slot functions in a weakmap during the 1st
  51. // pass and complete them in the 2nd pass.
  52. const wipMap = new WeakMap<ComponentNode, WIPSlotEntry[]>()
  53. interface WIPSlotEntry {
  54. fn: FunctionExpression
  55. children: TemplateChildNode[]
  56. vnodeBranch: ReturnStatement
  57. }
  58. const componentTypeMap = new WeakMap<ComponentNode, symbol>()
  59. // ssr component transform is done in two phases:
  60. // In phase 1. we use `buildSlot` to analyze the children of the component into
  61. // WIP slot functions (it must be done in phase 1 because `buildSlot` relies on
  62. // the core transform context).
  63. // In phase 2. we convert the WIP slots from phase 1 into ssr-specific codegen
  64. // nodes.
  65. export const ssrTransformComponent: NodeTransform = (node, context) => {
  66. if (
  67. node.type !== NodeTypes.ELEMENT ||
  68. node.tagType !== ElementTypes.COMPONENT
  69. ) {
  70. return
  71. }
  72. const component = resolveComponentType(node, context, true /* ssr */)
  73. if (isSymbol(component)) {
  74. componentTypeMap.set(node, component)
  75. if (component === SUSPENSE) {
  76. return ssrTransformSuspense(node, context)
  77. }
  78. return // built-in component: fallthrough
  79. }
  80. // Build the fallback vnode-based branch for the component's slots.
  81. // We need to clone the node into a fresh copy and use the buildSlots' logic
  82. // to get access to the children of each slot. We then compile them with
  83. // a child transform pipeline using vnode-based transforms (instead of ssr-
  84. // based ones), and save the result branch (a ReturnStatement) in an array.
  85. // The branch is retrieved when processing slots again in ssr mode.
  86. const vnodeBranches: ReturnStatement[] = []
  87. const clonedNode = clone(node)
  88. return function ssrPostTransformComponent() {
  89. // Using the cloned node, build the normal VNode-based branches (for
  90. // fallback in case the child is render-fn based). Store them in an array
  91. // for later use.
  92. if (clonedNode.children.length) {
  93. buildSlots(clonedNode, context, (props, children) => {
  94. vnodeBranches.push(createVNodeSlotBranch(props, children, context))
  95. return createFunctionExpression(undefined)
  96. })
  97. }
  98. const props =
  99. node.props.length > 0
  100. ? // note we are not passing ssr: true here because for components, v-on
  101. // handlers should still be passed
  102. buildProps(node, context).props || `null`
  103. : `null`
  104. const wipEntries: WIPSlotEntry[] = []
  105. wipMap.set(node, wipEntries)
  106. const buildSSRSlotFn: SlotFnBuilder = (props, children, loc) => {
  107. const fn = createFunctionExpression(
  108. [props || `_`, `_push`, `_parent`, `_scopeId`],
  109. undefined, // no return, assign body later
  110. true, // newline
  111. true, // isSlot
  112. loc
  113. )
  114. wipEntries.push({
  115. fn,
  116. children,
  117. // also collect the corresponding vnode branch built earlier
  118. vnodeBranch: vnodeBranches[wipEntries.length]
  119. })
  120. return fn
  121. }
  122. const slots = node.children.length
  123. ? buildSlots(node, context, buildSSRSlotFn).slots
  124. : `null`
  125. node.ssrCodegenNode = createCallExpression(
  126. context.helper(SSR_RENDER_COMPONENT),
  127. [component, props, slots, `_parent`]
  128. )
  129. }
  130. }
  131. export function ssrProcessComponent(
  132. node: ComponentNode,
  133. context: SSRTransformContext
  134. ) {
  135. if (!node.ssrCodegenNode) {
  136. // this is a built-in component that fell-through.
  137. const component = componentTypeMap.get(node)!
  138. if (component === TELEPORT) {
  139. return ssrProcessTeleport(node, context)
  140. } else if (component === SUSPENSE) {
  141. return ssrProcessSuspense(node, context)
  142. } else {
  143. // real fall-through (e.g. KeepAlive): just render its children.
  144. processChildren(node.children, context, component === TRANSITION_GROUP)
  145. }
  146. } else {
  147. // finish up slot function expressions from the 1st pass.
  148. const wipEntries = wipMap.get(node) || []
  149. for (let i = 0; i < wipEntries.length; i++) {
  150. const { fn, children, vnodeBranch } = wipEntries[i]
  151. // For each slot, we generate two branches: one SSR-optimized branch and
  152. // one normal vnode-based branch. The branches are taken based on the
  153. // presence of the 2nd `_push` argument (which is only present if the slot
  154. // is called by `_ssrRenderSlot`.
  155. fn.body = createIfStatement(
  156. createSimpleExpression(`_push`, false),
  157. processChildrenAsStatement(
  158. children,
  159. context,
  160. false,
  161. true /* withSlotScopeId */
  162. ),
  163. vnodeBranch
  164. )
  165. }
  166. context.pushStatement(createCallExpression(`_push`, [node.ssrCodegenNode]))
  167. }
  168. }
  169. export const rawOptionsMap = new WeakMap<RootNode, CompilerOptions>()
  170. const [baseNodeTransforms, baseDirectiveTransforms] = getBaseTransformPreset(
  171. true
  172. )
  173. const vnodeNodeTransforms = [...baseNodeTransforms, ...DOMNodeTransforms]
  174. const vnodeDirectiveTransforms = {
  175. ...baseDirectiveTransforms,
  176. ...DOMDirectiveTransforms
  177. }
  178. function createVNodeSlotBranch(
  179. props: ExpressionNode | undefined,
  180. children: TemplateChildNode[],
  181. parentContext: TransformContext
  182. ): ReturnStatement {
  183. // apply a sub-transform using vnode-based transforms.
  184. const rawOptions = rawOptionsMap.get(parentContext.root)!
  185. const subOptions = {
  186. ...rawOptions,
  187. // overwrite with vnode-based transforms
  188. nodeTransforms: [
  189. ...vnodeNodeTransforms,
  190. ...(rawOptions.nodeTransforms || [])
  191. ],
  192. directiveTransforms: {
  193. ...vnodeDirectiveTransforms,
  194. ...(rawOptions.directiveTransforms || {})
  195. }
  196. }
  197. // wrap the children with a wrapper template for proper children treatment.
  198. const wrapperNode: TemplateNode = {
  199. type: NodeTypes.ELEMENT,
  200. ns: Namespaces.HTML,
  201. tag: 'template',
  202. tagType: ElementTypes.TEMPLATE,
  203. isSelfClosing: false,
  204. // important: provide v-slot="props" on the wrapper for proper
  205. // scope analysis
  206. props: [
  207. {
  208. type: NodeTypes.DIRECTIVE,
  209. name: 'slot',
  210. exp: props,
  211. arg: undefined,
  212. modifiers: [],
  213. loc: locStub
  214. }
  215. ],
  216. children,
  217. loc: locStub,
  218. codegenNode: undefined
  219. }
  220. subTransform(wrapperNode, subOptions, parentContext)
  221. return createReturnStatement(children)
  222. }
  223. function subTransform(
  224. node: TemplateChildNode,
  225. options: TransformOptions,
  226. parentContext: TransformContext
  227. ) {
  228. const childRoot = createRoot([node])
  229. const childContext = createTransformContext(childRoot, options)
  230. // this sub transform is for vnode fallback branch so it should be handled
  231. // like normal render functions
  232. childContext.ssr = false
  233. // inherit parent scope analysis state
  234. childContext.scopes = { ...parentContext.scopes }
  235. childContext.identifiers = { ...parentContext.identifiers }
  236. // traverse
  237. traverseNode(childRoot, childContext)
  238. // merge helpers/components/directives/imports into parent context
  239. ;(['helpers', 'components', 'directives', 'imports'] as const).forEach(
  240. key => {
  241. childContext[key].forEach((value: any) => {
  242. ;(parentContext[key] as any).add(value)
  243. })
  244. }
  245. )
  246. }
  247. function clone(v: any): any {
  248. if (isArray(v)) {
  249. return v.map(clone)
  250. } else if (isObject(v)) {
  251. const res: any = {}
  252. for (const key in v) {
  253. res[key] = clone(v[key])
  254. }
  255. return res
  256. } else {
  257. return v
  258. }
  259. }