diff --git a/packages/compiler-ssr/src/runtimeHelpers.ts b/packages/compiler-ssr/src/runtimeHelpers.ts index 1d5609974..b986ce7f2 100644 --- a/packages/compiler-ssr/src/runtimeHelpers.ts +++ b/packages/compiler-ssr/src/runtimeHelpers.ts @@ -5,7 +5,8 @@ export const SSR_RENDER_COMPONENT = Symbol(`renderComponent`) export const SSR_RENDER_SLOT = Symbol(`renderSlot`) export const SSR_RENDER_CLASS = Symbol(`renderClass`) 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`) // Note: these are helpers imported from @vue/server-renderer @@ -16,6 +17,7 @@ registerRuntimeHelpers({ [SSR_RENDER_SLOT]: `_renderSlot`, [SSR_RENDER_CLASS]: `_renderClass`, [SSR_RENDER_STYLE]: `_renderStyle`, - [SSR_RENDER_PROPS]: `_renderProps`, + [SSR_RENDER_ATTRS]: `renderAttrs`, + [SSR_RENDER_ATTR]: `renderAttr`, [SSR_RENDER_LIST]: `_renderList` }) diff --git a/packages/server-renderer/__tests__/interpolate.spec.ts b/packages/server-renderer/__tests__/interpolate.spec.ts index a9c8fb344..c3a5ae41e 100644 --- a/packages/server-renderer/__tests__/interpolate.spec.ts +++ b/packages/server-renderer/__tests__/interpolate.spec.ts @@ -1,16 +1,16 @@ -import { _interpolate } from '../src' +import { interpolate } from '../src/helpers/interpolate' import { escapeHtml } from '@vue/shared' test('ssr: interpolate', () => { - expect(_interpolate(0)).toBe(`0`) - expect(_interpolate(`foo`)).toBe(`foo`) - expect(_interpolate(`
`)).toBe(`<div>`) + expect(interpolate(0)).toBe(`0`) + expect(interpolate(`foo`)).toBe(`foo`) + expect(interpolate(`
`)).toBe(`<div>`) // should escape interpolated values - expect(_interpolate([1, 2, 3])).toBe( + expect(interpolate([1, 2, 3])).toBe( escapeHtml(JSON.stringify([1, 2, 3], null, 2)) ) expect( - _interpolate({ + interpolate({ foo: 1, bar: `
` }) diff --git a/packages/server-renderer/__tests__/renderList.spec.ts b/packages/server-renderer/__tests__/renderList.spec.ts new file mode 100644 index 000000000..700f5eb94 --- /dev/null +++ b/packages/server-renderer/__tests__/renderList.spec.ts @@ -0,0 +1,3 @@ +test('ssr: renderList', () => { + // TODO +}) diff --git a/packages/server-renderer/__tests__/renderProps.spec.ts b/packages/server-renderer/__tests__/renderProps.spec.ts index 6df970c61..de9588485 100644 --- a/packages/server-renderer/__tests__/renderProps.spec.ts +++ b/packages/server-renderer/__tests__/renderProps.spec.ts @@ -1,9 +1,13 @@ -import { renderProps, renderClass, renderStyle } from '../src/renderProps' +import { + renderAttrs, + renderClass, + renderStyle +} from '../src/helpers/renderAttrs' describe('ssr: renderProps', () => { test('ignore reserved props', () => { expect( - renderProps({ + renderAttrs({ key: 1, ref: () => {}, onClick: () => {} @@ -13,7 +17,7 @@ describe('ssr: renderProps', () => { test('normal attrs', () => { expect( - renderProps({ + renderAttrs({ id: 'foo', title: 'bar' }) @@ -22,7 +26,7 @@ describe('ssr: renderProps', () => { test('escape attrs', () => { expect( - renderProps({ + renderAttrs({ id: '"> { test('boolean attrs', () => { expect( - renderProps({ + renderAttrs({ checked: true, multiple: false }) @@ -39,7 +43,7 @@ describe('ssr: renderProps', () => { test('ignore falsy values', () => { expect( - renderProps({ + renderAttrs({ foo: false, title: null, baz: undefined @@ -49,7 +53,7 @@ describe('ssr: renderProps', () => { test('props to attrs', () => { expect( - renderProps({ + renderAttrs({ readOnly: true, // simple lower case conversion htmlFor: 'foobar' // special cases }) @@ -58,7 +62,7 @@ describe('ssr: renderProps', () => { test('preserve name on custom element', () => { expect( - renderProps( + renderAttrs( { fooBar: 'ok' }, @@ -71,7 +75,7 @@ describe('ssr: renderProps', () => { describe('ssr: renderClass', () => { test('via renderProps', () => { expect( - renderProps({ + renderAttrs({ class: ['foo', 'bar'] }) ).toBe(` class="foo bar"`) @@ -92,7 +96,7 @@ describe('ssr: renderClass', () => { describe('ssr: renderStyle', () => { test('via renderProps', () => { expect( - renderProps({ + renderAttrs({ style: { color: 'red' } diff --git a/packages/server-renderer/src/helpers/interpolate.ts b/packages/server-renderer/src/helpers/interpolate.ts new file mode 100644 index 000000000..17d8c35ae --- /dev/null +++ b/packages/server-renderer/src/helpers/interpolate.ts @@ -0,0 +1,5 @@ +import { escapeHtml, toDisplayString } from '@vue/shared' + +export function interpolate(value: unknown): string { + return escapeHtml(toDisplayString(value)) +} diff --git a/packages/server-renderer/src/renderProps.ts b/packages/server-renderer/src/helpers/renderAttrs.ts similarity index 71% rename from packages/server-renderer/src/renderProps.ts rename to packages/server-renderer/src/helpers/renderAttrs.ts index 4830aca35..4549903cb 100644 --- a/packages/server-renderer/src/renderProps.ts +++ b/packages/server-renderer/src/helpers/renderAttrs.ts @@ -14,7 +14,7 @@ import { const shouldIgnoreProp = makeMap(`key,ref,innerHTML,textContent`) -export function renderProps( +export function renderAttrs( props: Record, tag?: string ): string { @@ -32,23 +32,30 @@ export function renderProps( ret += ` class="${renderClass(value)}"` } else if (key === 'style') { ret += ` style="${renderStyle(value)}"` - } else if (value != null) { - const attrKey = - 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)}"` - } + } else { + ret += renderAttr(key, value, tag) } } 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 { return escapeHtml(normalizeClass(raw)) } diff --git a/packages/server-renderer/src/helpers/renderList.ts b/packages/server-renderer/src/helpers/renderList.ts new file mode 100644 index 000000000..8b4a0c7f8 --- /dev/null +++ b/packages/server-renderer/src/helpers/renderList.ts @@ -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) + 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) + } + } + } +} diff --git a/packages/server-renderer/src/index.ts b/packages/server-renderer/src/index.ts index defb3af77..cec208316 100644 --- a/packages/server-renderer/src/index.ts +++ b/packages/server-renderer/src/index.ts @@ -1,7 +1,7 @@ // public export { renderToString } from './renderToString' -// internal +// internal runtime helpers export { renderComponent as _renderComponent, renderSlot as _renderSlot @@ -9,12 +9,7 @@ export { export { renderClass as _renderClass, renderStyle as _renderStyle, - renderProps as _renderProps -} from './renderProps' - -// utils -import { escapeHtml, toDisplayString } from '@vue/shared' - -export function _interpolate(value: unknown): string { - return escapeHtml(toDisplayString(value)) -} + renderAttrs as _renderAttrs +} from './helpers/renderAttrs' +export { interpolate as _interpolate } from './helpers/interpolate' +export { renderList as _renderList } from './helpers/renderList' diff --git a/packages/server-renderer/src/renderToString.ts b/packages/server-renderer/src/renderToString.ts index b649e4db7..3d0f01244 100644 --- a/packages/server-renderer/src/renderToString.ts +++ b/packages/server-renderer/src/renderToString.ts @@ -19,10 +19,10 @@ import { isPromise, isArray, isFunction, - isVoidTag + isVoidTag, + escapeHtml } from '@vue/shared' -import { renderProps } from './renderProps' -import { escapeHtml } from '@vue/shared' +import { renderAttrs } from './helpers/renderAttrs' const { isVNode, @@ -213,7 +213,7 @@ function renderElement( // TODO directives if (props !== null) { - openTag += renderProps(props, tag) + openTag += renderAttrs(props, tag) } if (scopeId !== null) {