Skip to content

Commit

Permalink
support directly rendering async components in SSR
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed May 24, 2017
1 parent 7404091 commit 9cf6646
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 22 deletions.
19 changes: 13 additions & 6 deletions src/core/vdom/create-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ const hooksToMerge = Object.keys(componentVNodeHooks)

export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data?: VNodeData,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
Expand All @@ -123,20 +123,27 @@ export function createComponent (
return
}
data = data || {}
// async component
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
if (Ctor === undefined) {
// return nothing if this is indeed an async component
// wait for the callback to trigger parent update.
return createAsyncPlaceholder(asyncFactory, data.key)
// return a placeholder node for async component, which is rendered
// as a comment node but preserves all the raw information for the node.
// the information will be used for async server-rendering and hydration.
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
data = data || {}
// resolve constructor options in case global mixins are applied after
// component constructor creation
resolveConstructorOptions(Ctor)
Expand Down
7 changes: 5 additions & 2 deletions src/core/vdom/helpers/resolve-async-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ function ensureCtor (comp, base) {

export function createAsyncPlaceholder (
factory: Function,
key: string | number | void
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag: ?string
): VNode {
const node = createEmptyVNode()
node.asyncFactory = factory
node.key = key
node.asyncMeta = { data, context, children, tag }
return node
}

Expand Down
3 changes: 3 additions & 0 deletions src/core/vdom/vnode.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ export default class VNode {
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
asyncFactory: ?Function; // async component factory function
asyncMeta: ?Object;
isAsyncPlaceholder: boolean;
ssrContext: ?Object;

constructor (
tag?: string,
Expand Down Expand Up @@ -51,6 +53,7 @@ export default class VNode {
this.isCloned = false
this.isOnce = false
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}

Expand Down
86 changes: 72 additions & 14 deletions src/server/render.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
/* @flow */

import {
isDef,
isUndef,
isTrue
} from 'shared/util'

import { escape } from 'web/server/util'
import { SSR_ATTR } from 'shared/constants'
import { RenderContext } from './render-context'
import { ssrCompileToFunctions } from 'web/server/compiler'
import { installSSRHelpers } from './optimizing-compiler/runtime-helpers'
import { createComponentInstanceForVnode } from 'core/vdom/create-component'

import { isDef, isUndef, isTrue } from 'shared/util'
import {
createComponent,
createComponentInstanceForVnode
} from 'core/vdom/create-component'

let warned = Object.create(null)
const warnOnce = msg => {
Expand Down Expand Up @@ -39,20 +47,20 @@ function renderNode (node, isRoot, context) {
renderStringNode(node, context)
} else if (isDef(node.componentOptions)) {
renderComponent(node, isRoot, context)
} else {
if (isDef(node.tag)) {
renderElement(node, isRoot, context)
} else if (isTrue(node.isComment)) {
context.write(
`<!--${node.text}-->`,
context.next
)
} else if (isDef(node.tag)) {
renderElement(node, isRoot, context)
} else if (isTrue(node.isComment)) {
if (isDef(node.asyncFactory)) {
// async component
renderAsyncComponent(node, isRoot, context)
} else {
context.write(
node.raw ? node.text : escape(String(node.text)),
context.next
)
context.write(`<!--${node.text}-->`, context.next)
}
} else {
context.write(
node.raw ? node.text : escape(String(node.text)),
context.next
)
}
}

Expand Down Expand Up @@ -160,6 +168,56 @@ function renderComponentInner (node, isRoot, context) {
renderNode(childNode, isRoot, context)
}

function renderAsyncComponent (node, isRoot, context) {
const factory = node.asyncFactory

const resolve = comp => {
const { data, children, tag } = node.asyncMeta
const nodeContext = node.asyncMeta.context
const resolvedNode: any = createComponent(
comp,
data,
nodeContext,
children,
tag
)
if (resolvedNode) {
renderComponent(resolvedNode, isRoot, context)
} else {
reject()
}
}

const reject = err => {
console.error(`[vue-server-renderer] error when rendering async component:\n`)
if (err) console.error(err.stack)
context.write(`<!--${node.text}-->`, context.next)
}

if (factory.resolved) {
resolve(factory.resolved)
return
}

let res
try {
res = factory(resolve, reject)
} catch (e) {
reject(e)
}
if (res) {
if (typeof res.then === 'function') {
res.then(resolve, reject).catch(reject)
} else {
// new syntax in 2.3
const comp = res.component
if (comp && typeof comp.then === 'function') {
comp.then(resolve, reject).catch(reject)
}
}
}
}

function renderStringNode (el, context) {
const { write, next } = context
if (isUndef(el.children) || el.children.length === 0) {
Expand Down

0 comments on commit 9cf6646

Please sign in to comment.