refactor(ssr): adjust helper structure + renderList

This commit is contained in:
Evan You 2020-02-03 18:16:09 -05:00
parent 889a0276eb
commit 2ad0eed5cd
9 changed files with 90 additions and 45 deletions

View File

@ -5,7 +5,8 @@ export const SSR_RENDER_COMPONENT = Symbol(`renderComponent`)
export const SSR_RENDER_SLOT = Symbol(`renderSlot`) export const SSR_RENDER_SLOT = Symbol(`renderSlot`)
export const SSR_RENDER_CLASS = Symbol(`renderClass`) export const SSR_RENDER_CLASS = Symbol(`renderClass`)
export const SSR_RENDER_STYLE = Symbol(`renderStyle`) export const SSR_RENDER_STYLE = Symbol(`renderStyle`)
export const SSR_RENDER_PROPS = Symbol(`renderProps`) export const SSR_RENDER_ATTRS = Symbol(`renderAttrs`)
export const SSR_RENDER_ATTR = Symbol(`renderAttr`)
export const SSR_RENDER_LIST = Symbol(`renderList`) export const SSR_RENDER_LIST = Symbol(`renderList`)
// Note: these are helpers imported from @vue/server-renderer // Note: these are helpers imported from @vue/server-renderer
@ -16,6 +17,7 @@ registerRuntimeHelpers({
[SSR_RENDER_SLOT]: `_renderSlot`, [SSR_RENDER_SLOT]: `_renderSlot`,
[SSR_RENDER_CLASS]: `_renderClass`, [SSR_RENDER_CLASS]: `_renderClass`,
[SSR_RENDER_STYLE]: `_renderStyle`, [SSR_RENDER_STYLE]: `_renderStyle`,
[SSR_RENDER_PROPS]: `_renderProps`, [SSR_RENDER_ATTRS]: `renderAttrs`,
[SSR_RENDER_ATTR]: `renderAttr`,
[SSR_RENDER_LIST]: `_renderList` [SSR_RENDER_LIST]: `_renderList`
}) })

View File

@ -1,16 +1,16 @@
import { _interpolate } from '../src' import { interpolate } from '../src/helpers/interpolate'
import { escapeHtml } from '@vue/shared' import { escapeHtml } from '@vue/shared'
test('ssr: interpolate', () => { test('ssr: interpolate', () => {
expect(_interpolate(0)).toBe(`0`) expect(interpolate(0)).toBe(`0`)
expect(_interpolate(`foo`)).toBe(`foo`) expect(interpolate(`foo`)).toBe(`foo`)
expect(_interpolate(`<div>`)).toBe(`&lt;div&gt;`) expect(interpolate(`<div>`)).toBe(`&lt;div&gt;`)
// should escape interpolated values // should escape interpolated values
expect(_interpolate([1, 2, 3])).toBe( expect(interpolate([1, 2, 3])).toBe(
escapeHtml(JSON.stringify([1, 2, 3], null, 2)) escapeHtml(JSON.stringify([1, 2, 3], null, 2))
) )
expect( expect(
_interpolate({ interpolate({
foo: 1, foo: 1,
bar: `<div>` bar: `<div>`
}) })

View File

@ -0,0 +1,3 @@
test('ssr: renderList', () => {
// TODO
})

View File

@ -1,9 +1,13 @@
import { renderProps, renderClass, renderStyle } from '../src/renderProps' import {
renderAttrs,
renderClass,
renderStyle
} from '../src/helpers/renderAttrs'
describe('ssr: renderProps', () => { describe('ssr: renderProps', () => {
test('ignore reserved props', () => { test('ignore reserved props', () => {
expect( expect(
renderProps({ renderAttrs({
key: 1, key: 1,
ref: () => {}, ref: () => {},
onClick: () => {} onClick: () => {}
@ -13,7 +17,7 @@ describe('ssr: renderProps', () => {
test('normal attrs', () => { test('normal attrs', () => {
expect( expect(
renderProps({ renderAttrs({
id: 'foo', id: 'foo',
title: 'bar' title: 'bar'
}) })
@ -22,7 +26,7 @@ describe('ssr: renderProps', () => {
test('escape attrs', () => { test('escape attrs', () => {
expect( expect(
renderProps({ renderAttrs({
id: '"><script' id: '"><script'
}) })
).toBe(` id="&quot;&gt;&lt;script"`) ).toBe(` id="&quot;&gt;&lt;script"`)
@ -30,7 +34,7 @@ describe('ssr: renderProps', () => {
test('boolean attrs', () => { test('boolean attrs', () => {
expect( expect(
renderProps({ renderAttrs({
checked: true, checked: true,
multiple: false multiple: false
}) })
@ -39,7 +43,7 @@ describe('ssr: renderProps', () => {
test('ignore falsy values', () => { test('ignore falsy values', () => {
expect( expect(
renderProps({ renderAttrs({
foo: false, foo: false,
title: null, title: null,
baz: undefined baz: undefined
@ -49,7 +53,7 @@ describe('ssr: renderProps', () => {
test('props to attrs', () => { test('props to attrs', () => {
expect( expect(
renderProps({ renderAttrs({
readOnly: true, // simple lower case conversion readOnly: true, // simple lower case conversion
htmlFor: 'foobar' // special cases htmlFor: 'foobar' // special cases
}) })
@ -58,7 +62,7 @@ describe('ssr: renderProps', () => {
test('preserve name on custom element', () => { test('preserve name on custom element', () => {
expect( expect(
renderProps( renderAttrs(
{ {
fooBar: 'ok' fooBar: 'ok'
}, },
@ -71,7 +75,7 @@ describe('ssr: renderProps', () => {
describe('ssr: renderClass', () => { describe('ssr: renderClass', () => {
test('via renderProps', () => { test('via renderProps', () => {
expect( expect(
renderProps({ renderAttrs({
class: ['foo', 'bar'] class: ['foo', 'bar']
}) })
).toBe(` class="foo bar"`) ).toBe(` class="foo bar"`)
@ -92,7 +96,7 @@ describe('ssr: renderClass', () => {
describe('ssr: renderStyle', () => { describe('ssr: renderStyle', () => {
test('via renderProps', () => { test('via renderProps', () => {
expect( expect(
renderProps({ renderAttrs({
style: { style: {
color: 'red' color: 'red'
} }

View File

@ -0,0 +1,5 @@
import { escapeHtml, toDisplayString } from '@vue/shared'
export function interpolate(value: unknown): string {
return escapeHtml(toDisplayString(value))
}

View File

@ -14,7 +14,7 @@ import {
const shouldIgnoreProp = makeMap(`key,ref,innerHTML,textContent`) const shouldIgnoreProp = makeMap(`key,ref,innerHTML,textContent`)
export function renderProps( export function renderAttrs(
props: Record<string, unknown>, props: Record<string, unknown>,
tag?: string tag?: string
): string { ): string {
@ -32,23 +32,30 @@ export function renderProps(
ret += ` class="${renderClass(value)}"` ret += ` class="${renderClass(value)}"`
} else if (key === 'style') { } else if (key === 'style') {
ret += ` style="${renderStyle(value)}"` ret += ` style="${renderStyle(value)}"`
} else if (value != null) { } else {
const attrKey = ret += renderAttr(key, value, tag)
tag && tag.indexOf('-') > 0
? key // preserve raw name on custom elements
: propsToAttrMap[key] || key.toLowerCase()
if (isBooleanAttr(attrKey)) {
if (value !== false) {
ret += ` ${attrKey}`
}
} else if (isSSRSafeAttrName(attrKey)) {
ret += ` ${attrKey}="${escapeHtml(value)}"`
}
} }
} }
return ret return ret
} }
export function renderAttr(key: string, value: unknown, tag?: string): string {
if (value == null) {
return ``
}
const attrKey =
tag && tag.indexOf('-') > 0
? key // preserve raw name on custom elements
: propsToAttrMap[key] || key.toLowerCase()
if (isBooleanAttr(attrKey)) {
return value === false ? `` : ` ${attrKey}`
} else if (isSSRSafeAttrName(attrKey)) {
return ` ${attrKey}="${escapeHtml(value)}"`
} else {
return ``
}
}
export function renderClass(raw: unknown): string { export function renderClass(raw: unknown): string {
return escapeHtml(normalizeClass(raw)) return escapeHtml(normalizeClass(raw))
} }

View File

@ -0,0 +1,29 @@
import { isArray, isString, isObject } from '@vue/shared'
export function renderList(
source: unknown,
renderItem: (value: unknown, key: string | number, index?: number) => void
) {
if (isArray(source) || isString(source)) {
for (let i = 0, l = source.length; i < l; i++) {
renderItem(source[i], i)
}
} else if (typeof source === 'number') {
for (let i = 0; i < source; i++) {
renderItem(i + 1, i)
}
} else if (isObject(source)) {
if (source[Symbol.iterator as any]) {
const arr = Array.from(source as Iterable<any>)
for (let i = 0, l = arr.length; i < l; i++) {
renderItem(arr[i], i)
}
} else {
const keys = Object.keys(source)
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
renderItem(source[key], key, i)
}
}
}
}

View File

@ -1,7 +1,7 @@
// public // public
export { renderToString } from './renderToString' export { renderToString } from './renderToString'
// internal // internal runtime helpers
export { export {
renderComponent as _renderComponent, renderComponent as _renderComponent,
renderSlot as _renderSlot renderSlot as _renderSlot
@ -9,12 +9,7 @@ export {
export { export {
renderClass as _renderClass, renderClass as _renderClass,
renderStyle as _renderStyle, renderStyle as _renderStyle,
renderProps as _renderProps renderAttrs as _renderAttrs
} from './renderProps' } from './helpers/renderAttrs'
export { interpolate as _interpolate } from './helpers/interpolate'
// utils export { renderList as _renderList } from './helpers/renderList'
import { escapeHtml, toDisplayString } from '@vue/shared'
export function _interpolate(value: unknown): string {
return escapeHtml(toDisplayString(value))
}

View File

@ -19,10 +19,10 @@ import {
isPromise, isPromise,
isArray, isArray,
isFunction, isFunction,
isVoidTag isVoidTag,
escapeHtml
} from '@vue/shared' } from '@vue/shared'
import { renderProps } from './renderProps' import { renderAttrs } from './helpers/renderAttrs'
import { escapeHtml } from '@vue/shared'
const { const {
isVNode, isVNode,
@ -213,7 +213,7 @@ function renderElement(
// TODO directives // TODO directives
if (props !== null) { if (props !== null) {
openTag += renderProps(props, tag) openTag += renderAttrs(props, tag)
} }
if (scopeId !== null) { if (scopeId !== null) {