ソースを参照

support async hydration

Evan You 9 年 前
コミット
7404091896

+ 9 - 5
src/core/vdom/create-component.js

@@ -15,6 +15,7 @@ import {
 
 import {
   resolveAsyncComponent,
+  createAsyncPlaceholder,
   extractPropsFromVNodeData
 } from './helpers/index'
 
@@ -122,13 +123,17 @@ export function createComponent (
     return
   }
 
+  data = data || {}
+
   // async component
+  let asyncFactory
   if (isUndef(Ctor.cid)) {
-    Ctor = resolveAsyncComponent(Ctor, baseCtor, context)
+    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
+      return createAsyncPlaceholder(asyncFactory, data.key)
     }
   }
 
@@ -136,8 +141,6 @@ export function createComponent (
   // component constructor creation
   resolveConstructorOptions(Ctor)
 
-  data = data || {}
-
   // transform component v-model data into props & events
   if (isDef(data.model)) {
     transformModel(Ctor.options, data)
@@ -171,7 +174,8 @@ export function createComponent (
   const vnode = new VNode(
     `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
     data, undefined, undefined, undefined, context,
-    { Ctor, propsData, listeners, tag, children }
+    { Ctor, propsData, listeners, tag, children },
+    asyncFactory
   )
   return vnode
 }

+ 12 - 0
src/core/vdom/helpers/resolve-async-component.js

@@ -9,12 +9,24 @@ import {
   isObject
 } from 'core/util/index'
 
+import { createEmptyVNode } from 'core/vdom/vnode'
+
 function ensureCtor (comp, base) {
   return isObject(comp)
     ? base.extend(comp)
     : comp
 }
 
+export function createAsyncPlaceholder (
+  factory: Function,
+  key: string | number | void
+): VNode {
+  const node = createEmptyVNode()
+  node.asyncFactory = factory
+  node.key = key
+  return node
+}
+
 export function resolveAsyncComponent (
   factory: Function,
   baseCtor: Class<Component>,

+ 19 - 5
src/core/vdom/patch.js

@@ -33,11 +33,17 @@ const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
 
 function sameVnode (a, b) {
   return (
-    a.key === b.key &&
-    a.tag === b.tag &&
-    a.isComment === b.isComment &&
-    isDef(a.data) === isDef(b.data) &&
-    sameInputType(a, b)
+    a.key === b.key && (
+      (
+        a.isAsyncPlaceholder === true &&
+        a.asyncFactory === b.asyncFactory
+      ) || (
+        a.tag === b.tag &&
+        a.isComment === b.isComment &&
+        isDef(a.data) === isDef(b.data) &&
+        sameInputType(a, b)
+      )
+    )
   )
 }
 
@@ -434,6 +440,9 @@ export function createPatchFunction (backend) {
     if (oldVnode === vnode) {
       return
     }
+    if (oldVnode.isAsyncPlaceholder) {
+      return hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
+    }
     // reuse element for static trees.
     // note we only do this if the vnode is cloned -
     // if the new node is not cloned it means the render functions have been
@@ -497,6 +506,11 @@ export function createPatchFunction (backend) {
 
   // Note: this is a browser-only function so we can assume elms are DOM nodes.
   function hydrate (elm, vnode, insertedVnodeQueue) {
+    if (isTrue(vnode.isComment) && isDef(vnode.asyncFactory)) {
+      vnode.elm = elm
+      vnode.isAsyncPlaceholder = true
+      return true
+    }
     if (process.env.NODE_ENV !== 'production') {
       if (!assertNodeMatch(elm, vnode)) {
         return false

+ 6 - 1
src/core/vdom/vnode.js

@@ -19,6 +19,8 @@ export default class VNode {
   isComment: boolean; // empty comment placeholder?
   isCloned: boolean; // is a cloned node?
   isOnce: boolean; // is a v-once node?
+  asyncFactory: ?Function; // async component factory function
+  isAsyncPlaceholder: boolean;
 
   constructor (
     tag?: string,
@@ -27,7 +29,8 @@ export default class VNode {
     text?: string,
     elm?: Node,
     context?: Component,
-    componentOptions?: VNodeComponentOptions
+    componentOptions?: VNodeComponentOptions,
+    asyncFactory?: Function
   ) {
     this.tag = tag
     this.data = data
@@ -47,6 +50,8 @@ export default class VNode {
     this.isComment = false
     this.isCloned = false
     this.isOnce = false
+    this.asyncFactory = asyncFactory
+    this.isAsyncPlaceholder = false
   }
 
   // DEPRECATED: alias for componentInstance for backwards compat.

+ 4 - 3
test/unit/modules/vdom/create-component.spec.js

@@ -61,7 +61,8 @@ describe('create-component', () => {
     }
     function go () {
       vnode = createComponent(async, data, vm, vm)
-      expect(vnode).toBeUndefined() // not to be loaded yet.
+      expect(vnode.isComment).toBe(true) // not to be loaded yet.
+      expect(vnode.asyncFactory).toBe(async)
     }
     function loaded () {
       vnode = createComponent(async, data, vm, vm)
@@ -93,11 +94,11 @@ describe('create-component', () => {
     }
     function go () {
       vnode = createComponent(async, data, vm, vm)
-      expect(vnode).toBeUndefined() // not to be loaded yet.
+      expect(vnode.isComment).toBe(true) // not to be loaded yet.
     }
     function failed () {
       vnode = createComponent(async, data, vm, vm)
-      expect(vnode).toBeUndefined() // failed
+      expect(vnode.isComment).toBe(true) // failed, still a comment node
       expect(`Failed to resolve async component: ${async}\nReason: ${reason}`).toHaveBeenWarned()
       done()
     }