]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(ssr): restructure
authorEvan You <yyx990803@gmail.com>
Mon, 27 Jan 2020 22:23:42 +0000 (17:23 -0500)
committerEvan You <yyx990803@gmail.com>
Mon, 27 Jan 2020 22:23:42 +0000 (17:23 -0500)
packages/runtime-core/src/index.ts
packages/runtime-core/src/vnode.ts
packages/server-renderer/__tests__/escape.spec.ts [new file with mode: 0644]
packages/server-renderer/__tests__/renderToString.spec.ts [new file with mode: 0644]
packages/server-renderer/__tests__/renderVnode.spec.ts [new file with mode: 0644]
packages/server-renderer/src/escape.ts [moved from packages/server-renderer/src/helpers.ts with 86% similarity]
packages/server-renderer/src/index.ts
packages/server-renderer/src/renderToString.ts [new file with mode: 0644]
packages/server-renderer/src/renderVnode.ts [new file with mode: 0644]
packages/shared/src/index.ts
packages/shared/src/normalizeProp.ts [new file with mode: 0644]

index d3b24f2bdde422cb9f03ac172f5b246a3efcd1c5..72460ddcafe13ab1cab39d79cc3cc5780f6629a4 100644 (file)
@@ -103,6 +103,7 @@ export { registerRuntimeCompiler } from './component'
 
 // For server-renderer
 export { createComponentInstance, setupComponent } from './component'
+export { renderComponentRoot } from './componentRenderUtils'
 
 // Types -----------------------------------------------------------------------
 
index 33c177d6f5c7ebe01002f04464b3ff3ae307fc76..c23ddd80106ace1cdd78b035e1cc807401b54fce 100644 (file)
@@ -4,7 +4,9 @@ import {
   isString,
   isObject,
   EMPTY_ARR,
-  extend
+  extend,
+  normalizeClass,
+  normalizeStyle
 } from '@vue/shared'
 import {
   ComponentInternalInstance,
@@ -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)[]) {
diff --git a/packages/server-renderer/__tests__/escape.spec.ts b/packages/server-renderer/__tests__/escape.spec.ts
new file mode 100644 (file)
index 0000000..6e4c6a2
--- /dev/null
@@ -0,0 +1 @@
+test('ssr: escape HTML', () => {})
diff --git a/packages/server-renderer/__tests__/renderToString.spec.ts b/packages/server-renderer/__tests__/renderToString.spec.ts
new file mode 100644 (file)
index 0000000..4a7dc68
--- /dev/null
@@ -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', () => {})
+})
diff --git a/packages/server-renderer/__tests__/renderVnode.spec.ts b/packages/server-renderer/__tests__/renderVnode.spec.ts
new file mode 100644 (file)
index 0000000..038b933
--- /dev/null
@@ -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
+    })
+  })
+})
similarity index 86%
rename from packages/server-renderer/src/helpers.ts
rename to packages/server-renderer/src/escape.ts
index 761dc10e765ba4d174e528dd6255aa2630696cda..6cfe940393a7909112a1ab7ce714455f82d5f195 100644 (file)
@@ -1,5 +1,3 @@
-import { toDisplayString } from '@vue/shared'
-
 const escapeRE = /["'&<>]/
 
 export function escape(string: unknown) {
@@ -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))
-}
index 318a0fa3495d7136c4e23f576be8450297242718..8546d2471ad2c2dae6f99b7a57b8ead990f7dcfc 100644 (file)
-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))
 }
diff --git a/packages/server-renderer/src/renderToString.ts b/packages/server-renderer/src/renderToString.ts
new file mode 100644 (file)
index 0000000..b35ca8f
--- /dev/null
@@ -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)
+}
diff --git a/packages/server-renderer/src/renderVnode.ts b/packages/server-renderer/src/renderVnode.ts
new file mode 100644 (file)
index 0000000..09f32df
--- /dev/null
@@ -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() {}
index 89f21923e8601f8ca1373a1202861c2ea6ebb5ac..55b5d3ea7da8e4cbf386813d2b2e02a806d891da 100644 (file)
@@ -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({})
diff --git a/packages/shared/src/normalizeProp.ts b/packages/shared/src/normalizeProp.ts
new file mode 100644 (file)
index 0000000..8829710
--- /dev/null
@@ -0,0 +1,38 @@
+import { isArray, isString, isObject } from './'
+
+export 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()
+}