File tree 8 files changed +109
-10
lines changed
8 files changed +109
-10
lines changed Original file line number Diff line number Diff line change @@ -309,5 +309,18 @@ describe('ssr: components', () => {
309
309
}"
310
310
` )
311
311
} )
312
+
313
+ test ( 'portal rendering' , ( ) => {
314
+ expect ( compile ( `<portal :target="target"><div/></portal>` ) . code )
315
+ . toMatchInlineSnapshot ( `
316
+ "const { ssrRenderPortal: _ssrRenderPortal } = require(\\"@vue/server-renderer\\")
317
+
318
+ return function ssrRender(_ctx, _push, _parent) {
319
+ _ssrRenderPortal((_push) => {
320
+ _push(\`<div></div>\`)
321
+ }, _ctx.target, _parent)
322
+ }"
323
+ ` )
324
+ } )
312
325
} )
313
326
} )
Original file line number Diff line number Diff line change @@ -18,10 +18,12 @@ export function createSSRCompilerError(
18
18
19
19
export const enum SSRErrorCodes {
20
20
X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM = DOMErrorCodes . __EXTEND_POINT__ ,
21
- X_SSR_UNSAFE_ATTR_NAME
21
+ X_SSR_UNSAFE_ATTR_NAME ,
22
+ X_SSR_NO_PORTAL_TARGET
22
23
}
23
24
24
25
export const SSRErrorMessages : { [ code : number ] : string } = {
25
26
[ SSRErrorCodes . X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM ] : `Custom directive is missing corresponding SSR transform and will be ignored.` ,
26
- [ SSRErrorCodes . X_SSR_UNSAFE_ATTR_NAME ] : `Unsafe attribute name for SSR.`
27
+ [ SSRErrorCodes . X_SSR_UNSAFE_ATTR_NAME ] : `Unsafe attribute name for SSR.` ,
28
+ [ SSRErrorCodes . X_SSR_NO_PORTAL_TARGET ] : `No target prop on portal element.`
27
29
}
Original file line number Diff line number Diff line change @@ -13,6 +13,7 @@ export const SSR_LOOSE_EQUAL = Symbol(`ssrLooseEqual`)
13
13
export const SSR_LOOSE_CONTAIN = Symbol ( `ssrLooseContain` )
14
14
export const SSR_RENDER_DYNAMIC_MODEL = Symbol ( `ssrRenderDynamicModel` )
15
15
export const SSR_GET_DYNAMIC_MODEL_PROPS = Symbol ( `ssrGetDynamicModelProps` )
16
+ export const SSR_RENDER_PORTAL = Symbol ( `ssrRenderPortal` )
16
17
17
18
export const ssrHelpers = {
18
19
[ SSR_INTERPOLATE ] : `ssrInterpolate` ,
@@ -27,7 +28,8 @@ export const ssrHelpers = {
27
28
[ SSR_LOOSE_EQUAL ] : `ssrLooseEqual` ,
28
29
[ SSR_LOOSE_CONTAIN ] : `ssrLooseContain` ,
29
30
[ SSR_RENDER_DYNAMIC_MODEL ] : `ssrRenderDynamicModel` ,
30
- [ SSR_GET_DYNAMIC_MODEL_PROPS ] : `ssrGetDynamicModelProps`
31
+ [ SSR_GET_DYNAMIC_MODEL_PROPS ] : `ssrGetDynamicModelProps` ,
32
+ [ SSR_RENDER_PORTAL ] : `ssrRenderPortal`
31
33
}
32
34
33
35
// Note: these are helpers imported from @vue /server-renderer
Original file line number Diff line number Diff line change @@ -31,9 +31,11 @@ import {
31
31
createTransformContext ,
32
32
traverseNode ,
33
33
ExpressionNode ,
34
- TemplateNode
34
+ TemplateNode ,
35
+ findProp ,
36
+ JSChildNode
35
37
} from '@vue/compiler-dom'
36
- import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
38
+ import { SSR_RENDER_COMPONENT , SSR_RENDER_PORTAL } from '../runtimeHelpers'
37
39
import {
38
40
SSRTransformContext ,
39
41
processChildren ,
@@ -134,7 +136,34 @@ export function ssrProcessComponent(
134
136
const component = componentTypeMap . get ( node ) !
135
137
136
138
if ( component === PORTAL ) {
137
- // TODO
139
+ const targetProp = findProp ( node , 'target' )
140
+ if ( ! targetProp ) return
141
+
142
+ let target : JSChildNode
143
+ if ( targetProp . type === NodeTypes . ATTRIBUTE && targetProp . value ) {
144
+ target = createSimpleExpression ( targetProp . value . content , true )
145
+ } else if ( targetProp . type === NodeTypes . DIRECTIVE && targetProp . exp ) {
146
+ target = targetProp . exp
147
+ } else {
148
+ return
149
+ }
150
+
151
+ const contentRenderFn = createFunctionExpression (
152
+ [ `_push` ] ,
153
+ undefined , // Body is added later
154
+ true , // newline
155
+ false , // isSlot
156
+ node . loc
157
+ )
158
+ contentRenderFn . body = processChildrenAsStatement ( node . children , context )
159
+ context . pushStatement (
160
+ createCallExpression ( context . helper ( SSR_RENDER_PORTAL ) , [
161
+ contentRenderFn ,
162
+ target ,
163
+ `_parent`
164
+ ] )
165
+ )
166
+
138
167
return
139
168
}
140
169
Original file line number Diff line number Diff line change
1
+ import { createApp } from 'vue'
2
+ import { renderToString , SSRContext } from '../src/renderToString'
3
+ import { ssrRenderPortal } from '../src/helpers/ssrRenderPortal'
4
+
5
+ describe ( 'ssrRenderPortal' , ( ) => {
6
+ test ( 'portal rendering' , async ( ) => {
7
+ const ctx = {
8
+ portals : { }
9
+ } as SSRContext
10
+ await renderToString (
11
+ createApp ( {
12
+ data ( ) {
13
+ return { msg : 'hello' }
14
+ } ,
15
+ ssrRender ( _ctx , _push , _parent ) {
16
+ ssrRenderPortal (
17
+ _push => {
18
+ _push ( `<div>content</div>` )
19
+ } ,
20
+ '#target' ,
21
+ _parent
22
+ )
23
+ }
24
+ } ) ,
25
+ ctx
26
+ )
27
+ expect ( ctx . portals ! [ '#target' ] ) . toBe ( `<div>content</div>` )
28
+ } )
29
+ } )
Original file line number Diff line number Diff line change
1
+ import { ComponentInternalInstance , ssrContextKey } from 'vue'
2
+ import { SSRContext , createBuffer , PushFn } from '../renderToString'
3
+
4
+ export function ssrRenderPortal (
5
+ contentRenderFn : ( push : PushFn ) => void ,
6
+ target : string ,
7
+ parentComponent : ComponentInternalInstance
8
+ ) {
9
+ const { getBuffer, push } = createBuffer ( )
10
+
11
+ contentRenderFn ( push )
12
+
13
+ const context = parentComponent . appContext . provides [
14
+ ssrContextKey as any
15
+ ] as SSRContext
16
+ const portalBuffers =
17
+ context . __portalBuffers || ( context . __portalBuffers = { } )
18
+
19
+ portalBuffers [ target ] = getBuffer ( )
20
+ }
Original file line number Diff line number Diff line change @@ -13,6 +13,7 @@ export {
13
13
} from './helpers/ssrRenderAttrs'
14
14
export { ssrInterpolate } from './helpers/ssrInterpolate'
15
15
export { ssrRenderList } from './helpers/ssrRenderList'
16
+ export { ssrRenderPortal } from './helpers/ssrRenderPortal'
16
17
17
18
// v-model helpers
18
19
export {
Original file line number Diff line number Diff line change @@ -45,9 +45,12 @@ const {
45
45
// - A resolved buffer (recursive arrays of strings that can be unrolled
46
46
// synchronously)
47
47
// - An async buffer (a Promise that resolves to a resolved buffer)
48
- type SSRBuffer = SSRBufferItem [ ]
49
- type SSRBufferItem = string | ResolvedSSRBuffer | Promise < ResolvedSSRBuffer >
50
- type ResolvedSSRBuffer = ( string | ResolvedSSRBuffer ) [ ]
48
+ export type SSRBuffer = SSRBufferItem [ ]
49
+ export type SSRBufferItem =
50
+ | string
51
+ | ResolvedSSRBuffer
52
+ | Promise < ResolvedSSRBuffer >
53
+ export type ResolvedSSRBuffer = ( string | ResolvedSSRBuffer ) [ ]
51
54
52
55
export type PushFn = ( item : SSRBufferItem ) => void
53
56
@@ -62,7 +65,7 @@ export type SSRContext = {
62
65
>
63
66
}
64
67
65
- function createBuffer ( ) {
68
+ export function createBuffer ( ) {
66
69
let appendable = false
67
70
let hasAsync = false
68
71
const buffer : SSRBuffer = [ ]
You can’t perform that action at this time.
0 commit comments