renderToString.ts 2.6 KB

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