Skip to content

Commit 8cdaf28

Browse files
committed
test(ssr): test for rendering components
1 parent 6e06810 commit 8cdaf28

File tree

4 files changed

+266
-20
lines changed

4 files changed

+266
-20
lines changed

packages/runtime-core/src/apiOptions.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,12 @@ export interface ComponentOptionsBase<
6464
// type.
6565
render?: Function
6666
// SSR only. This is produced by compiler-ssr and attached in compiler-sfc
67-
ssrRender?: Function
67+
// not user facing, so the typing is lax and for test only.
68+
ssrRender?: (
69+
ctx: any,
70+
push: (item: any) => void,
71+
parentInstance: ComponentInternalInstance
72+
) => void
6873
components?: Record<
6974
string,
7075
Component | { new (): ComponentPublicInstance<any, any, any, any, any> }
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,271 @@
1-
// import { renderToString, renderComponent } from '../src'
1+
import { createApp, h } from 'vue'
2+
import { renderToString, renderComponent, renderSlot } from '../src'
23

34
describe('ssr: renderToString', () => {
4-
describe('elements', () => {
5-
test('text children', () => {})
5+
describe('components', () => {
6+
test('vnode components', async () => {
7+
expect(
8+
await renderToString(
9+
createApp({
10+
data() {
11+
return { msg: 'hello' }
12+
},
13+
render(this: any) {
14+
return h('div', this.msg)
15+
}
16+
})
17+
)
18+
).toBe(`<div>hello</div>`)
19+
})
620

7-
test('array children', () => {})
21+
test('optimized components', async () => {
22+
expect(
23+
await renderToString(
24+
createApp({
25+
data() {
26+
return { msg: 'hello' }
27+
},
28+
ssrRender(ctx, push) {
29+
push(`<div>${ctx.msg}</div>`)
30+
}
31+
})
32+
)
33+
).toBe(`<div>hello</div>`)
34+
})
835

9-
test('void elements', () => {})
36+
test('nested vnode components', async () => {
37+
const Child = {
38+
props: ['msg'],
39+
render(this: any) {
40+
return h('div', this.msg)
41+
}
42+
}
1043

11-
test('innerHTML', () => {})
44+
expect(
45+
await renderToString(
46+
createApp({
47+
render() {
48+
return h('div', ['parent', h(Child, { msg: 'hello' })])
49+
}
50+
})
51+
)
52+
).toBe(`<div>parent<div>hello</div></div>`)
53+
})
1254

13-
test('textContent', () => {})
55+
test('nested optimized components', async () => {
56+
const Child = {
57+
props: ['msg'],
58+
ssrRender(ctx: any, push: any) {
59+
push(`<div>${ctx.msg}</div>`)
60+
}
61+
}
1462

15-
test('textarea value', () => {})
63+
expect(
64+
await renderToString(
65+
createApp({
66+
ssrRender(_ctx, push, parent) {
67+
push(`<div>parent`)
68+
push(renderComponent(Child, { msg: 'hello' }, null, parent))
69+
push(`</div>`)
70+
}
71+
})
72+
)
73+
).toBe(`<div>parent<div>hello</div></div>`)
74+
})
75+
76+
test('mixing optimized / vnode components', async () => {
77+
const OptimizedChild = {
78+
props: ['msg'],
79+
ssrRender(ctx: any, push: any) {
80+
push(`<div>${ctx.msg}</div>`)
81+
}
82+
}
83+
84+
const VNodeChild = {
85+
props: ['msg'],
86+
render(this: any) {
87+
return h('div', this.msg)
88+
}
89+
}
90+
91+
expect(
92+
await renderToString(
93+
createApp({
94+
ssrRender(_ctx, push, parent) {
95+
push(`<div>parent`)
96+
push(
97+
renderComponent(OptimizedChild, { msg: 'opt' }, null, parent)
98+
)
99+
push(renderComponent(VNodeChild, { msg: 'vnode' }, null, parent))
100+
push(`</div>`)
101+
}
102+
})
103+
)
104+
).toBe(`<div>parent<div>opt</div><div>vnode</div></div>`)
105+
})
106+
107+
test('nested components with optimized slots', async () => {
108+
const Child = {
109+
props: ['msg'],
110+
ssrRender(ctx: any, push: any, parent: any) {
111+
push(`<div class="child">`)
112+
renderSlot(ctx.$slots.default, { msg: 'from slot' }, push, parent)
113+
push(`</div>`)
114+
}
115+
}
116+
117+
expect(
118+
await renderToString(
119+
createApp({
120+
ssrRender(_ctx, push, parent) {
121+
push(`<div>parent`)
122+
push(
123+
renderComponent(
124+
Child,
125+
{ msg: 'hello' },
126+
{
127+
// optimized slot using string push
128+
default: ({ msg }: any, push: any) => {
129+
push(`<span>${msg}</span>`)
130+
},
131+
_compiled: true // important to avoid slots being normalized
132+
},
133+
parent
134+
)
135+
)
136+
push(`</div>`)
137+
}
138+
})
139+
)
140+
).toBe(
141+
`<div>parent<div class="child">` +
142+
`<!----><span>from slot</span><!---->` +
143+
`</div></div>`
144+
)
145+
})
146+
147+
test('nested components with vnode slots', async () => {
148+
const Child = {
149+
props: ['msg'],
150+
ssrRender(ctx: any, push: any, parent: any) {
151+
push(`<div class="child">`)
152+
renderSlot(ctx.$slots.default, { msg: 'from slot' }, push, parent)
153+
push(`</div>`)
154+
}
155+
}
156+
157+
expect(
158+
await renderToString(
159+
createApp({
160+
ssrRender(_ctx, push, parent) {
161+
push(`<div>parent`)
162+
push(
163+
renderComponent(
164+
Child,
165+
{ msg: 'hello' },
166+
{
167+
// bailed slots returning raw vnodes
168+
default: ({ msg }: any) => {
169+
return h('span', msg)
170+
}
171+
},
172+
parent
173+
)
174+
)
175+
push(`</div>`)
176+
}
177+
})
178+
)
179+
).toBe(
180+
`<div>parent<div class="child">` +
181+
`<!----><span>from slot</span><!---->` +
182+
`</div></div>`
183+
)
184+
})
185+
186+
test('async components', async () => {
187+
const Child = {
188+
// should wait for resovled render context from setup()
189+
async setup() {
190+
return {
191+
msg: 'hello'
192+
}
193+
},
194+
ssrRender(ctx: any, push: any) {
195+
push(`<div>${ctx.msg}</div>`)
196+
}
197+
}
198+
199+
expect(
200+
await renderToString(
201+
createApp({
202+
ssrRender(_ctx, push, parent) {
203+
push(`<div>parent`)
204+
push(renderComponent(Child, null, null, parent))
205+
push(`</div>`)
206+
}
207+
})
208+
)
209+
).toBe(`<div>parent<div>hello</div></div>`)
210+
})
211+
212+
test('parallel async components', async () => {
213+
const OptimizedChild = {
214+
props: ['msg'],
215+
async setup(props: any) {
216+
return {
217+
localMsg: props.msg + '!'
218+
}
219+
},
220+
ssrRender(ctx: any, push: any) {
221+
push(`<div>${ctx.localMsg}</div>`)
222+
}
223+
}
224+
225+
const VNodeChild = {
226+
props: ['msg'],
227+
async setup(props: any) {
228+
return {
229+
localMsg: props.msg + '!'
230+
}
231+
},
232+
render(this: any) {
233+
return h('div', this.localMsg)
234+
}
235+
}
236+
237+
expect(
238+
await renderToString(
239+
createApp({
240+
ssrRender(_ctx, push, parent) {
241+
push(`<div>parent`)
242+
push(
243+
renderComponent(OptimizedChild, { msg: 'opt' }, null, parent)
244+
)
245+
push(renderComponent(VNodeChild, { msg: 'vnode' }, null, parent))
246+
push(`</div>`)
247+
}
248+
})
249+
)
250+
).toBe(`<div>parent<div>opt!</div><div>vnode!</div></div>`)
251+
})
16252
})
17253

18-
describe('components', () => {
19-
test('nested components', () => {})
254+
describe('scopeId', () => {
255+
// TODO
256+
})
20257

21-
test('nested components with optimized slots', () => {})
258+
describe('vnode', () => {
259+
test('text children', () => {})
22260

23-
test('mixing optimized / vnode components', () => {})
261+
test('array children', () => {})
24262

25-
test('nested components with vnode slots', () => {})
263+
test('void elements', () => {})
26264

27-
test('async components', () => {})
265+
test('innerHTML', () => {})
28266

29-
test('parallel async components', () => {})
267+
test('textContent', () => {})
268+
269+
test('textarea value', () => {})
30270
})
31271
})

packages/server-renderer/src/renderToString.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ function renderComponentSubTree(
125125
} else {
126126
if (comp.ssrRender) {
127127
// optimized
128-
comp.ssrRender(push, instance.proxy)
128+
comp.ssrRender(instance.proxy, push, instance)
129129
} else if (comp.render) {
130130
renderVNode(push, renderComponentRoot(instance), instance)
131131
} else {
@@ -260,8 +260,8 @@ export function renderSlot(
260260
) {
261261
// template-compiled slots are always rendered as fragments
262262
push(`<!---->`)
263-
if (slotFn.length > 2) {
264-
// only ssr-optimized slot fns accept 3 arguments
263+
if (slotFn.length > 1) {
264+
// only ssr-optimized slot fns accept more than 1 arguments
265265
slotFn(slotProps, push, parentComponent)
266266
} else {
267267
// normal slot

tsconfig.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"types": ["jest", "puppeteer", "node"],
2121
"rootDir": ".",
2222
"paths": {
23-
"@vue/*": ["packages/*/src"]
23+
"@vue/*": ["packages/*/src"],
24+
"vue": ["packages/vue/src"]
2425
}
2526
},
2627
"include": [

0 commit comments

Comments
 (0)