Skip to content

Commit

Permalink
wip(ssr): restructure
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jan 27, 2020
1 parent d293876 commit 012bc5d
Show file tree
Hide file tree
Showing 11 changed files with 223 additions and 143 deletions.
1 change: 1 addition & 0 deletions packages/runtime-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export { registerRuntimeCompiler } from './component'

// For server-renderer
export { createComponentInstance, setupComponent } from './component'
export { renderComponentRoot } from './componentRenderUtils'

// Types -----------------------------------------------------------------------

Expand Down
41 changes: 3 additions & 38 deletions packages/runtime-core/src/vnode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {
isString,
isObject,
EMPTY_ARR,
extend
extend,
normalizeClass,
normalizeStyle
} from '@vue/shared'
import {
ComponentInternalInstance,
Expand Down Expand Up @@ -378,43 +380,6 @@ export function normalizeChildren(vnode: VNode, children: unknown) {
vnode.shapeFlag |= type
}

function normalizeStyle(
value: unknown
): Record<string, string | number> | void {
if (isArray(value)) {
const res: Record<string, string | number> = {}
for (let i = 0; i < value.length; i++) {
const normalized = normalizeStyle(value[i])
if (normalized) {
for (const key in normalized) {
res[key] = normalized[key]
}
}
}
return res
} else if (isObject(value)) {
return value
}
}

export function normalizeClass(value: unknown): string {
let res = ''
if (isString(value)) {
res = value
} else if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
res += normalizeClass(value[i]) + ' '
}
} else if (isObject(value)) {
for (const name in value) {
if (value[name]) {
res += name + ' '
}
}
}
return res.trim()
}

const handlersRE = /^on|^vnode/

export function mergeProps(...args: (Data & VNodeProps)[]) {
Expand Down
1 change: 1 addition & 0 deletions packages/server-renderer/__tests__/escape.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test('ssr: escape HTML', () => {})
17 changes: 17 additions & 0 deletions packages/server-renderer/__tests__/renderToString.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// import { renderToString, renderComponent } from '../src'

describe('ssr: renderToString', () => {
test('basic', () => {})

test('nested components', () => {})

test('nested components with optimized slots', () => {})

test('mixing optimized / vnode components', () => {})

test('nested components with vnode slots', () => {})

test('async components', () => {})

test('parallel async components', () => {})
})
29 changes: 29 additions & 0 deletions packages/server-renderer/__tests__/renderVnode.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
describe('ssr: render raw vnodes', () => {
test('class', () => {})

test('styles', () => {
// only render numbers for properties that allow no unit numbers
})

describe('attrs', () => {
test('basic', () => {})

test('boolean attrs', () => {})

test('enumerated attrs', () => {})

test('skip falsy values', () => {})
})

describe('domProps', () => {
test('innerHTML', () => {})

test('textContent', () => {})

test('textarea', () => {})

test('other renderable domProps', () => {
// also test camel to kebab case conversion for some props
})
})
})
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { toDisplayString } from '@vue/shared'

const escapeRE = /["'&<>]/

export function escape(string: unknown) {
Expand Down Expand Up @@ -45,7 +43,3 @@ export function escape(string: unknown) {

return lastIndex !== index ? html + str.substring(lastIndex, index) : html
}

export function interpolate(value: unknown) {
return escape(toDisplayString(value))
}
110 changes: 11 additions & 99 deletions packages/server-renderer/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,104 +1,16 @@
import {
App,
Component,
ComponentInternalInstance,
createComponentInstance,
setupComponent,
VNode,
createVNode
} from 'vue'
import { isString, isPromise, isArray } from '@vue/shared'
import { toDisplayString } from 'vue'

export * from './helpers'
export { renderToString, renderComponent } from './renderToString'

type SSRBuffer = SSRBufferItem[]
type SSRBufferItem = string | ResolvedSSRBuffer | Promise<SSRBuffer>
type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[]
export {
renderVNode,
renderClass,
renderStyle,
renderProps
} from './renderVnode'

function createBuffer() {
let appendable = false
let hasAsync = false
const buffer: SSRBuffer = []
return {
buffer,
hasAsync() {
return hasAsync
},
push(item: SSRBufferItem) {
const isStringItem = isString(item)
if (appendable && isStringItem) {
buffer[buffer.length - 1] += item as string
} else {
buffer.push(item)
}
appendable = isStringItem
if (!isStringItem && !isArray(item)) {
// promise
hasAsync = true
}
}
}
}

function unrollBuffer(buffer: ResolvedSSRBuffer): string {
let ret = ''
for (let i = 0; i < buffer.length; i++) {
const item = buffer[i]
if (isString(item)) {
ret += item
} else {
ret += unrollBuffer(item)
}
}
return ret
}

export async function renderToString(app: App): Promise<string> {
const resolvedBuffer = (await renderComponent(
app._component,
app._props
)) as ResolvedSSRBuffer
return unrollBuffer(resolvedBuffer)
}

export function renderComponent(
comp: Component,
props: Record<string, any> | null = null,
children: VNode['children'] = null,
parentComponent: ComponentInternalInstance | null = null
): ResolvedSSRBuffer | Promise<SSRBuffer> {
const vnode = createVNode(comp, props, children)
const instance = createComponentInstance(vnode, parentComponent)
const res = setupComponent(instance, null)
if (isPromise(res)) {
return res.then(() => innerRenderComponent(comp, instance))
} else {
return innerRenderComponent(comp, instance)
}
}
export { escape } from './escape'

function innerRenderComponent(
comp: Component,
instance: ComponentInternalInstance
): ResolvedSSRBuffer | Promise<SSRBuffer> {
const { buffer, push, hasAsync } = createBuffer()
if (typeof comp === 'function') {
// TODO FunctionalComponent
} else {
if (comp.ssrRender) {
// optimized
comp.ssrRender(push, instance.proxy)
} else if (comp.render) {
// TODO fallback to vdom serialization
} else {
// TODO warn component missing render function
}
}
// If the current component's buffer contains any Promise from async children,
// then it must return a Promise too. Otherwise this is a component that
// contains only sync children so we can avoid the async book-keeping overhead.
return hasAsync()
? // TS can't figure out the typing due to recursive appearance of Promise
Promise.all(buffer as any)
: (buffer as ResolvedSSRBuffer)
export function interpolate(value: unknown) {
return escape(toDisplayString(value))
}
109 changes: 109 additions & 0 deletions packages/server-renderer/src/renderToString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {
App,
Component,
ComponentInternalInstance,
VNode,
createComponentInstance,
setupComponent,
createVNode,
renderComponentRoot
} from 'vue'
import { isString, isPromise, isArray, isFunction } from '@vue/shared'
import { renderVNode } from './renderVnode'

export type SSRBuffer = SSRBufferItem[]
export type SSRBufferItem = string | ResolvedSSRBuffer | Promise<SSRBuffer>
export type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[]

function createBuffer() {
let appendable = false
let hasAsync = false
const buffer: SSRBuffer = []
return {
buffer,
hasAsync() {
return hasAsync
},
push(item: SSRBufferItem) {
const isStringItem = isString(item)
if (appendable && isStringItem) {
buffer[buffer.length - 1] += item as string
} else {
buffer.push(item)
}
appendable = isStringItem
if (!isStringItem && !isArray(item)) {
// promise
hasAsync = true
}
}
}
}

function unrollBuffer(buffer: ResolvedSSRBuffer): string {
let ret = ''
for (let i = 0; i < buffer.length; i++) {
const item = buffer[i]
if (isString(item)) {
ret += item
} else {
ret += unrollBuffer(item)
}
}
return ret
}

export async function renderToString(app: App): Promise<string> {
const resolvedBuffer = (await renderComponent(
app._component,
app._props
)) as ResolvedSSRBuffer
return unrollBuffer(resolvedBuffer)
}

export function renderComponent(
comp: Component,
props: Record<string, any> | null = null,
children: VNode['children'] = null,
parentComponent: ComponentInternalInstance | null = null
): ResolvedSSRBuffer | Promise<SSRBuffer> {
const vnode = createVNode(comp, props, children)
const instance = createComponentInstance(vnode, parentComponent)
const res = setupComponent(instance, null)
if (isPromise(res)) {
return res.then(() => innerRenderComponent(comp, instance))
} else {
return innerRenderComponent(comp, instance)
}
}

function innerRenderComponent(
comp: Component,
instance: ComponentInternalInstance
): ResolvedSSRBuffer | Promise<SSRBuffer> {
const { buffer, push, hasAsync } = createBuffer()
if (isFunction(comp)) {
renderVNode(push, renderComponentRoot(instance))
} else {
if (comp.ssrRender) {
// optimized
comp.ssrRender(push, instance.proxy)
} else if (comp.render) {
renderVNode(push, renderComponentRoot(instance))
} else {
// TODO on the fly template compilation support
throw new Error(
`Component ${
comp.name ? `${comp.name} ` : ``
} is missing render function.`
)
}
}
// If the current component's buffer contains any Promise from async children,
// then it must return a Promise too. Otherwise this is a component that
// contains only sync children so we can avoid the async book-keeping overhead.
return hasAsync()
? // TS can't figure out the typing due to recursive appearance of Promise
Promise.all(buffer as any)
: (buffer as ResolvedSSRBuffer)
}
13 changes: 13 additions & 0 deletions packages/server-renderer/src/renderVnode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { VNode } from 'vue'
import { SSRBufferItem } from './renderToString'

export function renderVNode(
push: (item: SSRBufferItem) => void,
vnode: VNode
) {}

export function renderProps() {}

export function renderClass() {}

export function renderStyle() {}
1 change: 1 addition & 0 deletions packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './globalsWhitelist'
export * from './codeframe'
export * from './domTagConfig'
export * from './mockWarn'
export * from './normalizeProp'

export const EMPTY_OBJ: { readonly [key: string]: any } = __DEV__
? Object.freeze({})
Expand Down
Loading

0 comments on commit 012bc5d

Please sign in to comment.