test(ssr): test rendering vnode elements
This commit is contained in:
parent
8cdaf28515
commit
eaf414f063
@ -104,13 +104,14 @@ export { registerRuntimeCompiler } from './component'
|
|||||||
// SSR -------------------------------------------------------------------------
|
// SSR -------------------------------------------------------------------------
|
||||||
import { createComponentInstance, setupComponent } from './component'
|
import { createComponentInstance, setupComponent } from './component'
|
||||||
import { renderComponentRoot } from './componentRenderUtils'
|
import { renderComponentRoot } from './componentRenderUtils'
|
||||||
import { normalizeVNode } from './vnode'
|
import { isVNode, normalizeVNode } from './vnode'
|
||||||
|
|
||||||
// SSR utils are only exposed in cjs builds.
|
// SSR utils are only exposed in cjs builds.
|
||||||
const _ssrUtils = {
|
const _ssrUtils = {
|
||||||
createComponentInstance,
|
createComponentInstance,
|
||||||
setupComponent,
|
setupComponent,
|
||||||
renderComponentRoot,
|
renderComponentRoot,
|
||||||
|
isVNode,
|
||||||
normalizeVNode
|
normalizeVNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +55,17 @@ describe('ssr: renderProps', () => {
|
|||||||
})
|
})
|
||||||
).toBe(` readonly for="foobar"`)
|
).toBe(` readonly for="foobar"`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('preserve name on custom element', () => {
|
||||||
|
expect(
|
||||||
|
renderProps(
|
||||||
|
{
|
||||||
|
fooBar: 'ok'
|
||||||
|
},
|
||||||
|
'my-el'
|
||||||
|
)
|
||||||
|
).toBe(` fooBar="ok"`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('ssr: renderClass', () => {
|
describe('ssr: renderClass', () => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createApp, h } from 'vue'
|
import { createApp, h, createCommentVNode } from 'vue'
|
||||||
import { renderToString, renderComponent, renderSlot } from '../src'
|
import { renderToString, renderComponent, renderSlot, escapeHtml } from '../src'
|
||||||
|
|
||||||
describe('ssr: renderToString', () => {
|
describe('ssr: renderToString', () => {
|
||||||
describe('components', () => {
|
describe('components', () => {
|
||||||
@ -251,21 +251,82 @@ describe('ssr: renderToString', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('vnode element', () => {
|
||||||
|
test('props', async () => {
|
||||||
|
expect(
|
||||||
|
await renderToString(
|
||||||
|
h('div', { id: 'foo&', class: ['bar', 'baz'] }, 'hello')
|
||||||
|
)
|
||||||
|
).toBe(`<div id="foo&" class="bar baz">hello</div>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('text children', async () => {
|
||||||
|
expect(await renderToString(h('div', 'hello'))).toBe(`<div>hello</div>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('array children', async () => {
|
||||||
|
expect(
|
||||||
|
await renderToString(
|
||||||
|
h('div', [
|
||||||
|
'foo',
|
||||||
|
h('span', 'bar'),
|
||||||
|
[h('span', 'baz')],
|
||||||
|
createCommentVNode('qux')
|
||||||
|
])
|
||||||
|
)
|
||||||
|
).toBe(
|
||||||
|
`<div>foo<span>bar</span><!----><span>baz</span><!----><!--qux--></div>`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('void elements', async () => {
|
||||||
|
expect(await renderToString(h('input'))).toBe(`<input>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('innerHTML', async () => {
|
||||||
|
expect(
|
||||||
|
await renderToString(
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
innerHTML: `<span>hello</span>`
|
||||||
|
},
|
||||||
|
'ignored'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).toBe(`<div><span>hello</span></div>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('textContent', async () => {
|
||||||
|
expect(
|
||||||
|
await renderToString(
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
textContent: `<span>hello</span>`
|
||||||
|
},
|
||||||
|
'ignored'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).toBe(`<div>${escapeHtml(`<span>hello</span>`)}</div>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('textarea value', async () => {
|
||||||
|
expect(
|
||||||
|
await renderToString(
|
||||||
|
h(
|
||||||
|
'textarea',
|
||||||
|
{
|
||||||
|
value: `<span>hello</span>`
|
||||||
|
},
|
||||||
|
'ignored'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).toBe(`<textarea>${escapeHtml(`<span>hello</span>`)}</textarea>`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('scopeId', () => {
|
describe('scopeId', () => {
|
||||||
// TODO
|
// TODO
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('vnode', () => {
|
|
||||||
test('text children', () => {})
|
|
||||||
|
|
||||||
test('array children', () => {})
|
|
||||||
|
|
||||||
test('void elements', () => {})
|
|
||||||
|
|
||||||
test('innerHTML', () => {})
|
|
||||||
|
|
||||||
test('textContent', () => {})
|
|
||||||
|
|
||||||
test('textarea value', () => {})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
@ -8,16 +8,23 @@ import {
|
|||||||
isNoUnitNumericStyleProp,
|
isNoUnitNumericStyleProp,
|
||||||
isOn,
|
isOn,
|
||||||
isSSRSafeAttrName,
|
isSSRSafeAttrName,
|
||||||
isBooleanAttr
|
isBooleanAttr,
|
||||||
|
makeMap
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
|
|
||||||
|
const shouldIgnoreProp = makeMap(`key,ref,innerHTML,textContent`)
|
||||||
|
|
||||||
export function renderProps(
|
export function renderProps(
|
||||||
props: Record<string, unknown>,
|
props: Record<string, unknown>,
|
||||||
isCustomElement: boolean = false
|
tag?: string
|
||||||
): string {
|
): string {
|
||||||
let ret = ''
|
let ret = ''
|
||||||
for (const key in props) {
|
for (const key in props) {
|
||||||
if (key === 'key' || key === 'ref' || isOn(key)) {
|
if (
|
||||||
|
shouldIgnoreProp(key) ||
|
||||||
|
isOn(key) ||
|
||||||
|
(tag === 'textarea' && key === 'value')
|
||||||
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const value = props[key]
|
const value = props[key]
|
||||||
@ -26,8 +33,9 @@ export function renderProps(
|
|||||||
} else if (key === 'style') {
|
} else if (key === 'style') {
|
||||||
ret += ` style="${renderStyle(value)}"`
|
ret += ` style="${renderStyle(value)}"`
|
||||||
} else if (value != null) {
|
} else if (value != null) {
|
||||||
const attrKey = isCustomElement
|
const attrKey =
|
||||||
? key
|
tag && tag.indexOf('-') > 0
|
||||||
|
? key // preserve raw name on custom elements
|
||||||
: propsToAttrMap[key] || key.toLowerCase()
|
: propsToAttrMap[key] || key.toLowerCase()
|
||||||
if (isBooleanAttr(attrKey)) {
|
if (isBooleanAttr(attrKey)) {
|
||||||
if (value !== false) {
|
if (value !== false) {
|
||||||
|
@ -12,7 +12,8 @@ import {
|
|||||||
Portal,
|
Portal,
|
||||||
ShapeFlags,
|
ShapeFlags,
|
||||||
ssrUtils,
|
ssrUtils,
|
||||||
Slot
|
Slot,
|
||||||
|
createApp
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
import {
|
import {
|
||||||
isString,
|
isString,
|
||||||
@ -25,6 +26,7 @@ import { renderProps } from './renderProps'
|
|||||||
import { escapeHtml } from './ssrUtils'
|
import { escapeHtml } from './ssrUtils'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
isVNode,
|
||||||
createComponentInstance,
|
createComponentInstance,
|
||||||
setupComponent,
|
setupComponent,
|
||||||
renderComponentRoot,
|
renderComponentRoot,
|
||||||
@ -81,7 +83,15 @@ function unrollBuffer(buffer: ResolvedSSRBuffer): string {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function renderToString(app: App): Promise<string> {
|
export async function renderToString(input: App | VNode): Promise<string> {
|
||||||
|
if (isVNode(input)) {
|
||||||
|
return renderAppToString(createApp({ render: () => input }))
|
||||||
|
} else {
|
||||||
|
return renderAppToString(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderAppToString(app: App): Promise<string> {
|
||||||
const resolvedBuffer = await renderComponent(app._component, app._props, null)
|
const resolvedBuffer = await renderComponent(app._component, app._props, null)
|
||||||
return unrollBuffer(resolvedBuffer)
|
return unrollBuffer(resolvedBuffer)
|
||||||
}
|
}
|
||||||
@ -143,7 +153,7 @@ function renderComponentSubTree(
|
|||||||
return hasAsync() ? Promise.all(buffer) : (buffer as ResolvedSSRBuffer)
|
return hasAsync() ? Promise.all(buffer) : (buffer as ResolvedSSRBuffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderVNode(
|
function renderVNode(
|
||||||
push: PushFn,
|
push: PushFn,
|
||||||
vnode: VNode,
|
vnode: VNode,
|
||||||
parentComponent: ComponentInternalInstance | null = null
|
parentComponent: ComponentInternalInstance | null = null
|
||||||
@ -203,7 +213,7 @@ function renderElement(
|
|||||||
// TODO directives
|
// TODO directives
|
||||||
|
|
||||||
if (props !== null) {
|
if (props !== null) {
|
||||||
openTag += renderProps(props, tag.indexOf(`-`) > 0)
|
openTag += renderProps(props, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scopeId !== null) {
|
if (scopeId !== null) {
|
||||||
|
Loading…
Reference in New Issue
Block a user