@@ -11,7 +11,8 @@ import {
11
11
Portal ,
12
12
ssrUtils ,
13
13
Slots ,
14
- warn
14
+ warn ,
15
+ createApp
15
16
} from 'vue'
16
17
import {
17
18
ShapeFlags ,
@@ -47,9 +48,22 @@ const {
47
48
type SSRBuffer = SSRBufferItem [ ]
48
49
type SSRBufferItem = string | ResolvedSSRBuffer | Promise < ResolvedSSRBuffer >
49
50
type ResolvedSSRBuffer = ( string | ResolvedSSRBuffer ) [ ]
51
+
50
52
export type PushFn = ( item : SSRBufferItem ) => void
53
+
51
54
export type Props = Record < string , unknown >
52
55
56
+ const ssrContextKey = Symbol ( )
57
+
58
+ export type SSRContext = {
59
+ [ key : string ] : any
60
+ portals ?: Record < string , string >
61
+ __portalBuffers ?: Record <
62
+ string ,
63
+ ResolvedSSRBuffer | Promise < ResolvedSSRBuffer >
64
+ >
65
+ }
66
+
53
67
function createBuffer ( ) {
54
68
let appendable = false
55
69
let hasAsync = false
@@ -88,17 +102,33 @@ function unrollBuffer(buffer: ResolvedSSRBuffer): string {
88
102
return ret
89
103
}
90
104
91
- export async function renderToString ( input : App | VNode ) : Promise < string > {
105
+ export async function renderToString (
106
+ input : App | VNode ,
107
+ context : SSRContext = { }
108
+ ) : Promise < string > {
92
109
let buffer : ResolvedSSRBuffer
93
110
if ( isVNode ( input ) ) {
94
- // raw vnode, wrap with component
95
- buffer = await renderComponent ( { render : ( ) => input } )
111
+ // raw vnode, wrap with app (for context)
112
+ return renderToString ( createApp ( { render : ( ) => input } ) , context )
96
113
} else {
97
114
// rendering an app
98
115
const vnode = createVNode ( input . _component , input . _props )
99
116
vnode . appContext = input . _context
117
+ // provide the ssr context to the tree
118
+ input . provide ( ssrContextKey , context )
100
119
buffer = await renderComponentVNode ( vnode )
101
120
}
121
+
122
+ // resolve portals
123
+ if ( context . __portalBuffers ) {
124
+ context . portals = context . portals || { }
125
+ for ( const key in context . __portalBuffers ) {
126
+ // note: it's OK to await sequentially here because the Promises were
127
+ // created eagerly in parallel.
128
+ context . portals [ key ] = unrollBuffer ( await context . __portalBuffers [ key ] )
129
+ }
130
+ }
131
+
102
132
return unrollBuffer ( buffer )
103
133
}
104
134
@@ -132,7 +162,7 @@ function renderComponentVNode(
132
162
}
133
163
134
164
type SSRRenderFunction = (
135
- ctx : any ,
165
+ context : any ,
136
166
push : ( item : any ) => void ,
137
167
parentInstance : ComponentInternalInstance
138
168
) => void
@@ -206,7 +236,7 @@ function renderComponentSubTree(
206
236
function renderVNode (
207
237
push : PushFn ,
208
238
vnode : VNode ,
209
- parentComponent : ComponentInternalInstance | null = null
239
+ parentComponent : ComponentInternalInstance
210
240
) {
211
241
const { type, shapeFlag, children } = vnode
212
242
switch ( type ) {
@@ -222,7 +252,7 @@ function renderVNode(
222
252
push ( `<!---->` )
223
253
break
224
254
case Portal :
225
- // TODO
255
+ renderPortal ( vnode , parentComponent )
226
256
break
227
257
default :
228
258
if ( shapeFlag & ShapeFlags . ELEMENT ) {
@@ -244,7 +274,7 @@ function renderVNode(
244
274
export function renderVNodeChildren (
245
275
push : PushFn ,
246
276
children : VNodeArrayChildren ,
247
- parentComponent : ComponentInternalInstance | null = null
277
+ parentComponent : ComponentInternalInstance
248
278
) {
249
279
for ( let i = 0 ; i < children . length ; i ++ ) {
250
280
renderVNode ( push , normalizeVNode ( children [ i ] ) , parentComponent )
@@ -254,7 +284,7 @@ export function renderVNodeChildren(
254
284
function renderElement (
255
285
push : PushFn ,
256
286
vnode : VNode ,
257
- parentComponent : ComponentInternalInstance | null = null
287
+ parentComponent : ComponentInternalInstance
258
288
) {
259
289
const tag = vnode . type as string
260
290
const { props, children, shapeFlag, scopeId } = vnode
@@ -305,3 +335,35 @@ function renderElement(
305
335
push ( `</${ tag } >` )
306
336
}
307
337
}
338
+
339
+ function renderPortal (
340
+ vnode : VNode ,
341
+ parentComponent : ComponentInternalInstance
342
+ ) {
343
+ const target = vnode . props && vnode . props . target
344
+ if ( ! target ) {
345
+ console . warn ( `[@vue/server-renderer] Portal is missing target prop.` )
346
+ return [ ]
347
+ }
348
+ if ( ! isString ( target ) ) {
349
+ console . warn (
350
+ `[@vue/server-renderer] Portal target must be a query selector string.`
351
+ )
352
+ return [ ]
353
+ }
354
+
355
+ const { buffer, push, hasAsync } = createBuffer ( )
356
+ renderVNodeChildren (
357
+ push ,
358
+ vnode . children as VNodeArrayChildren ,
359
+ parentComponent
360
+ )
361
+ const context = parentComponent . appContext . provides [
362
+ ssrContextKey as any
363
+ ] as SSRContext
364
+ const portalBuffers =
365
+ context . __portalBuffers || ( context . __portalBuffers = { } )
366
+ portalBuffers [ target ] = hasAsync ( )
367
+ ? Promise . all ( buffer )
368
+ : ( buffer as ResolvedSSRBuffer )
369
+ }
0 commit comments