renderToString.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import {
  2. type App,
  3. type VNode,
  4. createApp,
  5. createVNode,
  6. ssrContextKey,
  7. ssrUtils,
  8. } from 'vue'
  9. import { isPromise, isString } from '@vue/shared'
  10. import { type SSRBuffer, type SSRContext, renderComponentVNode } from './render'
  11. const { isVNode } = ssrUtils
  12. function nestedUnrollBuffer(
  13. buffer: SSRBuffer,
  14. parentRet: string,
  15. startIndex: number,
  16. ): Promise<string> | string {
  17. if (!buffer.hasAsync) {
  18. return parentRet + unrollBufferSync(buffer)
  19. }
  20. let ret = parentRet
  21. for (let i = startIndex; i < buffer.length; i += 1) {
  22. const item = buffer[i]
  23. if (isString(item)) {
  24. ret += item
  25. continue
  26. }
  27. if (isPromise(item)) {
  28. return item.then(nestedItem => {
  29. buffer[i] = nestedItem
  30. return nestedUnrollBuffer(buffer, ret, i)
  31. })
  32. }
  33. const result = nestedUnrollBuffer(item, ret, 0)
  34. if (isPromise(result)) {
  35. return result.then(nestedItem => {
  36. buffer[i] = nestedItem
  37. return nestedUnrollBuffer(buffer, '', i)
  38. })
  39. }
  40. ret = result
  41. }
  42. return ret
  43. }
  44. export function unrollBuffer(buffer: SSRBuffer): Promise<string> | string {
  45. return nestedUnrollBuffer(buffer, '', 0)
  46. }
  47. function unrollBufferSync(buffer: SSRBuffer): string {
  48. let ret = ''
  49. for (let i = 0; i < buffer.length; i++) {
  50. let item = buffer[i]
  51. if (isString(item)) {
  52. ret += item
  53. } else {
  54. // since this is a sync buffer, child buffers are never promises
  55. ret += unrollBufferSync(item as SSRBuffer)
  56. }
  57. }
  58. return ret
  59. }
  60. export async function renderToString(
  61. input: App | VNode,
  62. context: SSRContext = {},
  63. ): Promise<string> {
  64. if (isVNode(input)) {
  65. // raw vnode, wrap with app (for context)
  66. return renderToString(createApp({ render: () => input }), context)
  67. }
  68. // rendering an app
  69. const vnode = createVNode(input._component, input._props)
  70. vnode.appContext = input._context
  71. // provide the ssr context to the tree
  72. input.provide(ssrContextKey, context)
  73. const buffer = await renderComponentVNode(vnode)
  74. const result = await unrollBuffer(buffer as SSRBuffer)
  75. await resolveTeleports(context)
  76. if (context.__watcherHandles) {
  77. for (const unwatch of context.__watcherHandles) {
  78. unwatch()
  79. }
  80. }
  81. return result
  82. }
  83. export async function resolveTeleports(context: SSRContext): Promise<void> {
  84. if (context.__teleportBuffers) {
  85. context.teleports = context.teleports || {}
  86. for (const key in context.__teleportBuffers) {
  87. // note: it's OK to await sequentially here because the Promises were
  88. // created eagerly in parallel.
  89. context.teleports[key] = await unrollBuffer(
  90. await Promise.all([context.__teleportBuffers[key]]),
  91. )
  92. }
  93. }
  94. }