Skip to content

Commit eaf414f

Browse files
committed
test(ssr): test rendering vnode elements
1 parent 8cdaf28 commit eaf414f

File tree

5 files changed

+114
-23
lines changed

5 files changed

+114
-23
lines changed

packages/runtime-core/src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,14 @@ export { registerRuntimeCompiler } from './component'
104104
// SSR -------------------------------------------------------------------------
105105
import { createComponentInstance, setupComponent } from './component'
106106
import { renderComponentRoot } from './componentRenderUtils'
107-
import { normalizeVNode } from './vnode'
107+
import { isVNode, normalizeVNode } from './vnode'
108108

109109
// SSR utils are only exposed in cjs builds.
110110
const _ssrUtils = {
111111
createComponentInstance,
112112
setupComponent,
113113
renderComponentRoot,
114+
isVNode,
114115
normalizeVNode
115116
}
116117

packages/server-renderer/__tests__/renderProps.spec.ts

+11
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ describe('ssr: renderProps', () => {
5555
})
5656
).toBe(` readonly for="foobar"`)
5757
})
58+
59+
test('preserve name on custom element', () => {
60+
expect(
61+
renderProps(
62+
{
63+
fooBar: 'ok'
64+
},
65+
'my-el'
66+
)
67+
).toBe(` fooBar="ok"`)
68+
})
5869
})
5970

6071
describe('ssr: renderClass', () => {

packages/server-renderer/__tests__/renderToString.spec.ts

+73-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { createApp, h } from 'vue'
2-
import { renderToString, renderComponent, renderSlot } from '../src'
1+
import { createApp, h, createCommentVNode } from 'vue'
2+
import { renderToString, renderComponent, renderSlot, escapeHtml } from '../src'
33

44
describe('ssr: renderToString', () => {
55
describe('components', () => {
@@ -251,21 +251,82 @@ describe('ssr: renderToString', () => {
251251
})
252252
})
253253

254-
describe('scopeId', () => {
255-
// TODO
256-
})
254+
describe('vnode element', () => {
255+
test('props', async () => {
256+
expect(
257+
await renderToString(
258+
h('div', { id: 'foo&', class: ['bar', 'baz'] }, 'hello')
259+
)
260+
).toBe(`<div id="foo&amp;" class="bar baz">hello</div>`)
261+
})
257262

258-
describe('vnode', () => {
259-
test('text children', () => {})
263+
test('text children', async () => {
264+
expect(await renderToString(h('div', 'hello'))).toBe(`<div>hello</div>`)
265+
})
260266

261-
test('array children', () => {})
267+
test('array children', async () => {
268+
expect(
269+
await renderToString(
270+
h('div', [
271+
'foo',
272+
h('span', 'bar'),
273+
[h('span', 'baz')],
274+
createCommentVNode('qux')
275+
])
276+
)
277+
).toBe(
278+
`<div>foo<span>bar</span><!----><span>baz</span><!----><!--qux--></div>`
279+
)
280+
})
281+
282+
test('void elements', async () => {
283+
expect(await renderToString(h('input'))).toBe(`<input>`)
284+
})
262285

263-
test('void elements', () => {})
286+
test('innerHTML', async () => {
287+
expect(
288+
await renderToString(
289+
h(
290+
'div',
291+
{
292+
innerHTML: `<span>hello</span>`
293+
},
294+
'ignored'
295+
)
296+
)
297+
).toBe(`<div><span>hello</span></div>`)
298+
})
264299

265-
test('innerHTML', () => {})
300+
test('textContent', async () => {
301+
expect(
302+
await renderToString(
303+
h(
304+
'div',
305+
{
306+
textContent: `<span>hello</span>`
307+
},
308+
'ignored'
309+
)
310+
)
311+
).toBe(`<div>${escapeHtml(`<span>hello</span>`)}</div>`)
312+
})
266313

267-
test('textContent', () => {})
314+
test('textarea value', async () => {
315+
expect(
316+
await renderToString(
317+
h(
318+
'textarea',
319+
{
320+
value: `<span>hello</span>`
321+
},
322+
'ignored'
323+
)
324+
)
325+
).toBe(`<textarea>${escapeHtml(`<span>hello</span>`)}</textarea>`)
326+
})
327+
})
268328

269-
test('textarea value', () => {})
329+
describe('scopeId', () => {
330+
// TODO
270331
})
271332
})

packages/server-renderer/src/renderProps.ts

+14-6
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,23 @@ import {
88
isNoUnitNumericStyleProp,
99
isOn,
1010
isSSRSafeAttrName,
11-
isBooleanAttr
11+
isBooleanAttr,
12+
makeMap
1213
} from '@vue/shared'
1314

15+
const shouldIgnoreProp = makeMap(`key,ref,innerHTML,textContent`)
16+
1417
export function renderProps(
1518
props: Record<string, unknown>,
16-
isCustomElement: boolean = false
19+
tag?: string
1720
): string {
1821
let ret = ''
1922
for (const key in props) {
20-
if (key === 'key' || key === 'ref' || isOn(key)) {
23+
if (
24+
shouldIgnoreProp(key) ||
25+
isOn(key) ||
26+
(tag === 'textarea' && key === 'value')
27+
) {
2128
continue
2229
}
2330
const value = props[key]
@@ -26,9 +33,10 @@ export function renderProps(
2633
} else if (key === 'style') {
2734
ret += ` style="${renderStyle(value)}"`
2835
} else if (value != null) {
29-
const attrKey = isCustomElement
30-
? key
31-
: propsToAttrMap[key] || key.toLowerCase()
36+
const attrKey =
37+
tag && tag.indexOf('-') > 0
38+
? key // preserve raw name on custom elements
39+
: propsToAttrMap[key] || key.toLowerCase()
3240
if (isBooleanAttr(attrKey)) {
3341
if (value !== false) {
3442
ret += ` ${attrKey}`

packages/server-renderer/src/renderToString.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import {
1212
Portal,
1313
ShapeFlags,
1414
ssrUtils,
15-
Slot
15+
Slot,
16+
createApp
1617
} from 'vue'
1718
import {
1819
isString,
@@ -25,6 +26,7 @@ import { renderProps } from './renderProps'
2526
import { escapeHtml } from './ssrUtils'
2627

2728
const {
29+
isVNode,
2830
createComponentInstance,
2931
setupComponent,
3032
renderComponentRoot,
@@ -81,7 +83,15 @@ function unrollBuffer(buffer: ResolvedSSRBuffer): string {
8183
return ret
8284
}
8385

84-
export async function renderToString(app: App): Promise<string> {
86+
export async function renderToString(input: App | VNode): Promise<string> {
87+
if (isVNode(input)) {
88+
return renderAppToString(createApp({ render: () => input }))
89+
} else {
90+
return renderAppToString(input)
91+
}
92+
}
93+
94+
async function renderAppToString(app: App): Promise<string> {
8595
const resolvedBuffer = await renderComponent(app._component, app._props, null)
8696
return unrollBuffer(resolvedBuffer)
8797
}
@@ -143,7 +153,7 @@ function renderComponentSubTree(
143153
return hasAsync() ? Promise.all(buffer) : (buffer as ResolvedSSRBuffer)
144154
}
145155

146-
export function renderVNode(
156+
function renderVNode(
147157
push: PushFn,
148158
vnode: VNode,
149159
parentComponent: ComponentInternalInstance | null = null
@@ -203,7 +213,7 @@ function renderElement(
203213
// TODO directives
204214

205215
if (props !== null) {
206-
openTag += renderProps(props, tag.indexOf(`-`) > 0)
216+
openTag += renderProps(props, tag)
207217
}
208218

209219
if (scopeId !== null) {

0 commit comments

Comments
 (0)