renderToString.ts 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  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. async function unrollBuffer(buffer: SSRBuffer): Promise<string> {
  13. if (buffer.hasAsync) {
  14. let ret = ''
  15. for (let i = 0; i < buffer.length; i++) {
  16. let item = buffer[i]
  17. if (isPromise(item)) {
  18. item = await item
  19. }
  20. if (isString(item)) {
  21. ret += item
  22. } else {
  23. ret += await unrollBuffer(item)
  24. }
  25. }
  26. return ret
  27. } else {
  28. // sync buffer can be more efficiently unrolled without unnecessary await
  29. // ticks
  30. return unrollBufferSync(buffer)
  31. }
  32. }
  33. function unrollBufferSync(buffer: SSRBuffer): string {
  34. let ret = ''
  35. for (let i = 0; i < buffer.length; i++) {
  36. let item = buffer[i]
  37. if (isString(item)) {
  38. ret += item
  39. } else {
  40. // since this is a sync buffer, child buffers are never promises
  41. ret += unrollBufferSync(item as SSRBuffer)
  42. }
  43. }
  44. return ret
  45. }
  46. export async function renderToString(
  47. input: App | VNode,
  48. context: SSRContext = {},
  49. ): Promise<string> {
  50. if (isVNode(input)) {
  51. // raw vnode, wrap with app (for context)
  52. return renderToString(createApp({ render: () => input }), context)
  53. }
  54. // rendering an app
  55. const vnode = createVNode(input._component, input._props)
  56. vnode.appContext = input._context
  57. // provide the ssr context to the tree
  58. input.provide(ssrContextKey, context)
  59. const buffer = await renderComponentVNode(vnode)
  60. const result = await unrollBuffer(buffer as SSRBuffer)
  61. await resolveTeleports(context)
  62. if (context.__watcherHandles) {
  63. for (const unwatch of context.__watcherHandles) {
  64. unwatch()
  65. }
  66. }
  67. return result
  68. }
  69. export async function resolveTeleports(context: SSRContext) {
  70. if (context.__teleportBuffers) {
  71. context.teleports = context.teleports || {}
  72. for (const key in context.__teleportBuffers) {
  73. // note: it's OK to await sequentially here because the Promises were
  74. // created eagerly in parallel.
  75. context.teleports[key] = await unrollBuffer(
  76. await Promise.all([context.__teleportBuffers[key]]),
  77. )
  78. }
  79. }
  80. }