Skip to content

Commit da25517

Browse files
committed
wip(ssr): initial work on server-renderer
1 parent c07751f commit da25517

File tree

8 files changed

+110
-23
lines changed

8 files changed

+110
-23
lines changed

packages/runtime-core/src/apiCreateApp.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ export interface App<HostElement = any> {
1919
mount(rootContainer: HostElement | string): ComponentPublicInstance
2020
unmount(rootContainer: HostElement | string): void
2121
provide<T>(key: InjectionKey<T> | string, value: T): this
22-
rootComponent: Component
23-
rootContainer: HostElement | null
22+
23+
// internal. We need to expose these for the server-renderer
24+
_component: Component
25+
_props: Data | null
26+
_container: HostElement | null
2427
}
2528

2629
export interface AppConfig {
@@ -85,18 +88,21 @@ export type CreateAppFunction<HostElement> = (
8588
export function createAppAPI<HostNode, HostElement>(
8689
render: RootRenderFunction<HostNode, HostElement>
8790
): CreateAppFunction<HostElement> {
88-
return function createApp(
89-
rootComponent: Component,
90-
rootProps?: Data | null
91-
): App {
91+
return function createApp(rootComponent: Component, rootProps = null) {
92+
if (rootProps != null && !isObject(rootProps)) {
93+
__DEV__ && warn(`root props passed to app.mount() must be an object.`)
94+
rootProps = null
95+
}
96+
9297
const context = createAppContext()
9398
const installedPlugins = new Set()
9499

95100
let isMounted = false
96101

97102
const app: App = {
98-
rootComponent,
99-
rootContainer: null,
103+
_component: rootComponent,
104+
_props: rootProps,
105+
_container: null,
100106

101107
get config() {
102108
return context.config
@@ -176,11 +182,6 @@ export function createAppAPI<HostNode, HostElement>(
176182

177183
mount(rootContainer: HostElement): any {
178184
if (!isMounted) {
179-
if (rootProps != null && !isObject(rootProps)) {
180-
__DEV__ &&
181-
warn(`root props passed to app.mount() must be an object.`)
182-
rootProps = null
183-
}
184185
const vnode = createVNode(rootComponent, rootProps)
185186
// store app context on the root VNode.
186187
// this will be set on the root instance on initial mount.
@@ -195,7 +196,7 @@ export function createAppAPI<HostNode, HostElement>(
195196

196197
render(vnode, rootContainer)
197198
isMounted = true
198-
app.rootContainer = rootContainer
199+
app._container = rootContainer
199200
return vnode.component!.proxy
200201
} else if (__DEV__) {
201202
warn(
@@ -206,7 +207,7 @@ export function createAppAPI<HostNode, HostElement>(
206207

207208
unmount() {
208209
if (isMounted) {
209-
render(null, app.rootContainer!)
210+
render(null, app._container!)
210211
} else if (__DEV__) {
211212
warn(`Cannot unmount an app that is not mounted.`)
212213
}

packages/runtime-core/src/apiOptions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ export interface ComponentOptionsBase<
6363
// Luckily `render()` doesn't need any arguments nor does it care about return
6464
// type.
6565
render?: Function
66+
// SSR only. This is produced by compiler-ssr and attached in compiler-sfc
67+
ssrRender?: Function
6668
components?: Record<
6769
string,
6870
Component | { new (): ComponentPublicInstance<any, any, any, any, any> }

packages/runtime-core/src/component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ export interface ComponentInternalInstance {
154154

155155
const emptyAppContext = createAppContext()
156156

157-
export function defineComponentInstance(
157+
export function createComponentInstance(
158158
vnode: VNode,
159159
parent: ComponentInternalInstance | null
160160
) {

packages/runtime-core/src/componentProps.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ type NormalizedProp =
8585
type NormalizedPropsOptions = [Record<string, NormalizedProp>, string[]]
8686

8787
// resolve raw VNode data.
88-
// - filter out reserved keys (key, ref, slots)
88+
// - filter out reserved keys (key, ref)
8989
// - extract class and style into $attrs (to be merged onto child
9090
// component root)
9191
// - for the rest:

packages/runtime-core/src/renderer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
} from './vnode'
1313
import {
1414
ComponentInternalInstance,
15-
defineComponentInstance,
15+
createComponentInstance,
1616
setupStatefulComponent,
1717
Component,
1818
Data
@@ -927,7 +927,7 @@ export function createRenderer<
927927
parentSuspense: HostSuspenseBoundary | null,
928928
isSVG: boolean
929929
) {
930-
const instance: ComponentInternalInstance = (initialVNode.component = defineComponentInstance(
930+
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
931931
initialVNode,
932932
parentComponent
933933
))

packages/runtime-dom/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const createApp: CreateAppFunction<Element> = (...args) => {
3939
return
4040
}
4141
}
42-
const component = app.rootComponent
42+
const component = app._component
4343
if (
4444
__RUNTIME_COMPILE__ &&
4545
!isFunction(component) &&

packages/server-renderer/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,8 @@
2525
"bugs": {
2626
"url": "https://github.com/vuejs/vue/issues"
2727
},
28-
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/server-renderer#readme"
28+
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/server-renderer#readme",
29+
"peerDependencies": {
30+
"@vue/runtime-dom": "3.0.0-alpha.3"
31+
}
2932
}
Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,84 @@
1-
export function renderToString() {
2-
// TODO
1+
import {
2+
App,
3+
Component,
4+
ComponentInternalInstance,
5+
SuspenseBoundary
6+
} from '@vue/runtime-dom'
7+
import { isString } from '@vue/shared'
8+
9+
type SSRBuffer = SSRBufferItem[]
10+
type SSRBufferItem = string | Promise<SSRBuffer>
11+
type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[]
12+
13+
function createSSRBuffer() {
14+
let appendable = false
15+
const buffer: SSRBuffer = []
16+
return {
17+
buffer,
18+
push(item: SSRBufferItem) {
19+
const isStringItem = isString(item)
20+
if (appendable && isStringItem) {
21+
buffer[buffer.length - 1] += item as string
22+
} else {
23+
buffer.push(item)
24+
}
25+
appendable = isStringItem
26+
}
27+
}
28+
}
29+
30+
export async function renderToString(app: App): Promise<string> {
31+
const resolvedBuffer = (await renderComponent(
32+
app._component,
33+
app._props,
34+
null,
35+
null
36+
)) as ResolvedSSRBuffer
37+
return unrollBuffer(resolvedBuffer)
38+
}
39+
40+
function unrollBuffer(buffer: ResolvedSSRBuffer): string {
41+
let ret = ''
42+
for (let i = 0; i < buffer.length; i++) {
43+
const item = buffer[i]
44+
if (isString(item)) {
45+
ret += item
46+
} else {
47+
ret += unrollBuffer(item)
48+
}
49+
}
50+
return ret
51+
}
52+
53+
export async function renderComponent(
54+
comp: Component,
55+
props: Record<string, any> | null,
56+
parentComponent: ComponentInternalInstance | null,
57+
parentSuspense: SuspenseBoundary | null
58+
): Promise<SSRBuffer> {
59+
// 1. create component buffer
60+
const { buffer, push } = createSSRBuffer()
61+
62+
// 2. TODO create actual instance
63+
const instance = {
64+
proxy: {
65+
msg: 'hello'
66+
}
67+
}
68+
69+
if (typeof comp === 'function') {
70+
// TODO FunctionalComponent
71+
} else {
72+
if (comp.ssrRender) {
73+
// optimized
74+
comp.ssrRender(push, instance.proxy)
75+
} else if (comp.render) {
76+
// TODO fallback to vdom serialization
77+
} else {
78+
// TODO warn component missing render function
79+
}
80+
}
81+
// TS can't figure this out due to recursive occurance of Promise in type
82+
// @ts-ignore
83+
return Promise.all(buffer)
384
}

0 commit comments

Comments
 (0)