]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(ssr): initial work on server-renderer
authorEvan You <yyx990803@gmail.com>
Fri, 24 Jan 2020 02:01:56 +0000 (21:01 -0500)
committerEvan You <yyx990803@gmail.com>
Mon, 27 Jan 2020 21:00:17 +0000 (16:00 -0500)
packages/runtime-core/src/apiCreateApp.ts
packages/runtime-core/src/apiOptions.ts
packages/runtime-core/src/component.ts
packages/runtime-core/src/componentProps.ts
packages/runtime-core/src/renderer.ts
packages/runtime-dom/src/index.ts
packages/server-renderer/package.json
packages/server-renderer/src/index.ts

index a01b5ecb6083d5eb8db946cee745a47ba00bc926..234e4f7e84c63d28ac62069a4b88eb448a815047 100644 (file)
@@ -19,8 +19,11 @@ export interface App<HostElement = any> {
   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 {
@@ -85,18 +88,21 @@ export type CreateAppFunction<HostElement> = (
 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
@@ -176,11 +182,6 @@ export function createAppAPI<HostNode, HostElement>(
 
       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.
@@ -195,7 +196,7 @@ export function createAppAPI<HostNode, HostElement>(
 
           render(vnode, rootContainer)
           isMounted = true
-          app.rootContainer = rootContainer
+          app._container = rootContainer
           return vnode.component!.proxy
         } else if (__DEV__) {
           warn(
@@ -206,7 +207,7 @@ export function createAppAPI<HostNode, HostElement>(
 
       unmount() {
         if (isMounted) {
-          render(null, app.rootContainer!)
+          render(null, app._container!)
         } else if (__DEV__) {
           warn(`Cannot unmount an app that is not mounted.`)
         }
index 2de548cf3354515381ca1a4ec14f69a93dbcfb17..2cccb32c6b48ff8c1711ec7dae0ae2040b823015 100644 (file)
@@ -63,6 +63,8 @@ export interface ComponentOptionsBase<
   // 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> }
index b46d10ab550c1bdeb64ac09159da3dd5d6355451..fb0f3d68d3c73d19ef1dd95122fabf95f678995a 100644 (file)
@@ -154,7 +154,7 @@ export interface ComponentInternalInstance {
 
 const emptyAppContext = createAppContext()
 
-export function defineComponentInstance(
+export function createComponentInstance(
   vnode: VNode,
   parent: ComponentInternalInstance | null
 ) {
index a0acd982f2a29b765e6864e8e65adffcb2fa8106..baad88fd30cc6343c1351ea6c7add0ccbb5ee918 100644 (file)
@@ -85,7 +85,7 @@ type NormalizedProp =
 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:
index 7af49a21d93f6d73a25251cce40cba8ba9558595..2fb27c41a547f91b0c69d4771399ed78068ef339 100644 (file)
@@ -12,7 +12,7 @@ import {
 } from './vnode'
 import {
   ComponentInternalInstance,
-  defineComponentInstance,
+  createComponentInstance,
   setupStatefulComponent,
   Component,
   Data
@@ -927,7 +927,7 @@ export function createRenderer<
     parentSuspense: HostSuspenseBoundary | null,
     isSVG: boolean
   ) {
-    const instance: ComponentInternalInstance = (initialVNode.component = defineComponentInstance(
+    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
       initialVNode,
       parentComponent
     ))
index 51a8e0aa53daacfbe314eb79afd19ab75d99d5e5..9c9c1b59ff67aade340b14568ee16e506199d473 100644 (file)
@@ -39,7 +39,7 @@ export const createApp: CreateAppFunction<Element> = (...args) => {
         return
       }
     }
-    const component = app.rootComponent
+    const component = app._component
     if (
       __RUNTIME_COMPILE__ &&
       !isFunction(component) &&
index d0e695dfb2bca576c47b089c8e8753d55fd96f47..0e8743b4f6829583cca8878fa12d54bfd35d5106 100644 (file)
@@ -25,5 +25,8 @@
   "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"
+  }
 }
index 861ee73fac20afcedd89b9af46b2b5af6c7ead98..17c5f9beab6b2762cb181acf6a2826f5f0dee76e 100644 (file)
@@ -1,3 +1,84 @@
-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)
 }