mount(rootContainer: HostElement | string): ComponentPublicInstance
unmount(rootContainer: HostElement | string): void
provide<T>(key: InjectionKey<T> | string, value: T): this
- rootComponent: Component
- rootContainer: HostElement | null
+
+ // internal. We need to expose these for the server-renderer
+ _component: Component
+ _props: Data | null
+ _container: HostElement | null
}
export interface AppConfig {
export function createAppAPI<HostNode, HostElement>(
render: RootRenderFunction<HostNode, HostElement>
): CreateAppFunction<HostElement> {
- return function createApp(
- rootComponent: Component,
- rootProps?: Data | null
- ): App {
+ return function createApp(rootComponent: Component, rootProps = null) {
+ if (rootProps != null && !isObject(rootProps)) {
+ __DEV__ && warn(`root props passed to app.mount() must be an object.`)
+ rootProps = null
+ }
+
const context = createAppContext()
const installedPlugins = new Set()
let isMounted = false
const app: App = {
- rootComponent,
- rootContainer: null,
+ _component: rootComponent,
+ _props: rootProps,
+ _container: null,
get config() {
return context.config
mount(rootContainer: HostElement): any {
if (!isMounted) {
- if (rootProps != null && !isObject(rootProps)) {
- __DEV__ &&
- warn(`root props passed to app.mount() must be an object.`)
- rootProps = null
- }
const vnode = createVNode(rootComponent, rootProps)
// store app context on the root VNode.
// this will be set on the root instance on initial mount.
render(vnode, rootContainer)
isMounted = true
- app.rootContainer = rootContainer
+ app._container = rootContainer
return vnode.component!.proxy
} else if (__DEV__) {
warn(
unmount() {
if (isMounted) {
- render(null, app.rootContainer!)
+ render(null, app._container!)
} else if (__DEV__) {
warn(`Cannot unmount an app that is not mounted.`)
}
// Luckily `render()` doesn't need any arguments nor does it care about return
// type.
render?: Function
+ // SSR only. This is produced by compiler-ssr and attached in compiler-sfc
+ ssrRender?: Function
components?: Record<
string,
Component | { new (): ComponentPublicInstance<any, any, any, any, any> }
const emptyAppContext = createAppContext()
-export function defineComponentInstance(
+export function createComponentInstance(
vnode: VNode,
parent: ComponentInternalInstance | null
) {
type NormalizedPropsOptions = [Record<string, NormalizedProp>, string[]]
// resolve raw VNode data.
-// - filter out reserved keys (key, ref, slots)
+// - filter out reserved keys (key, ref)
// - extract class and style into $attrs (to be merged onto child
// component root)
// - for the rest:
} from './vnode'
import {
ComponentInternalInstance,
- defineComponentInstance,
+ createComponentInstance,
setupStatefulComponent,
Component,
Data
parentSuspense: HostSuspenseBoundary | null,
isSVG: boolean
) {
- const instance: ComponentInternalInstance = (initialVNode.component = defineComponentInstance(
+ const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
initialVNode,
parentComponent
))
return
}
}
- const component = app.rootComponent
+ const component = app._component
if (
__RUNTIME_COMPILE__ &&
!isFunction(component) &&
"bugs": {
"url": "https://github.com/vuejs/vue/issues"
},
- "homepage": "https://github.com/vuejs/vue/tree/dev/packages/server-renderer#readme"
+ "homepage": "https://github.com/vuejs/vue/tree/dev/packages/server-renderer#readme",
+ "peerDependencies": {
+ "@vue/runtime-dom": "3.0.0-alpha.3"
+ }
}
-export function renderToString() {
- // TODO
+import {
+ App,
+ Component,
+ ComponentInternalInstance,
+ SuspenseBoundary
+} from '@vue/runtime-dom'
+import { isString } from '@vue/shared'
+
+type SSRBuffer = SSRBufferItem[]
+type SSRBufferItem = string | Promise<SSRBuffer>
+type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[]
+
+function createSSRBuffer() {
+ let appendable = false
+ const buffer: SSRBuffer = []
+ return {
+ buffer,
+ push(item: SSRBufferItem) {
+ const isStringItem = isString(item)
+ if (appendable && isStringItem) {
+ buffer[buffer.length - 1] += item as string
+ } else {
+ buffer.push(item)
+ }
+ appendable = isStringItem
+ }
+ }
+}
+
+export async function renderToString(app: App): Promise<string> {
+ const resolvedBuffer = (await renderComponent(
+ app._component,
+ app._props,
+ null,
+ null
+ )) as ResolvedSSRBuffer
+ return unrollBuffer(resolvedBuffer)
+}
+
+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 renderComponent(
+ comp: Component,
+ props: Record<string, any> | null,
+ parentComponent: ComponentInternalInstance | null,
+ parentSuspense: SuspenseBoundary | null
+): Promise<SSRBuffer> {
+ // 1. create component buffer
+ const { buffer, push } = createSSRBuffer()
+
+ // 2. TODO create actual instance
+ const instance = {
+ proxy: {
+ msg: 'hello'
+ }
+ }
+
+ 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
+ }
+ }
+ // TS can't figure this out due to recursive occurance of Promise in type
+ // @ts-ignore
+ return Promise.all(buffer)
}