Просмотр исходного кода

feat: resolve ES module default when resolving async components

Evan You 9 лет назад
Родитель
Сommit
0cd6ef321b

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

@@ -12,6 +12,9 @@ import {
 import { createEmptyVNode } from 'core/vdom/vnode'
 
 function ensureCtor (comp, base) {
+  if (comp.__esModule && comp.default) {
+    comp = comp.default
+  }
   return isObject(comp)
     ? base.extend(comp)
     : comp

+ 3 - 0
src/server/render.js

@@ -172,6 +172,9 @@ function renderAsyncComponent (node, isRoot, context) {
   const factory = node.asyncFactory
 
   const resolve = comp => {
+    if (comp.__esModule && comp.default) {
+      comp = comp.default
+    }
     const { data, children, tag } = node.asyncMeta
     const nodeContext = node.asyncMeta.context
     const resolvedNode: any = createComponent(

+ 44 - 58
test/ssr/ssr-string.spec.js

@@ -473,7 +473,7 @@ describe('SSR: renderToString', () => {
     })
   })
 
-  it('renders asynchronous component', done => {
+  it('renders async component', done => {
     renderVmWithOptions({
       template: `
         <div>
@@ -482,11 +482,11 @@ describe('SSR: renderToString', () => {
       `,
       components: {
         testAsync (resolve) {
-          resolve({
+          setTimeout(() => resolve({
             render () {
               return this.$createElement('span', { class: ['b'] }, 'testAsync')
             }
-          })
+          }), 1)
         }
       }
     }, result => {
@@ -495,55 +495,58 @@ describe('SSR: renderToString', () => {
     })
   })
 
-  it('renders asynchronous component (hoc)', done => {
+  it('renders async component (Promise, nested)', done => {
+    const Foo = () => Promise.resolve({
+      render: h => h('div', [h('span', 'foo'), h(Bar)])
+    })
+    const Bar = () => ({
+      component: Promise.resolve({
+        render: h => h('span', 'bar')
+      })
+    })
     renderVmWithOptions({
-      template: '<test-async></test-async>',
-      components: {
-        testAsync (resolve) {
-          resolve({
-            render () {
-              return this.$createElement('span', { class: ['b'] }, 'testAsync')
-            }
-          })
-        }
+      render: h => h(Foo)
+    }, res => {
+      expect(res).toContain(`<div data-server-rendered="true"><span>foo</span><span>bar</span></div>`)
+      done()
+    })
+  })
+
+  it('renders async component (ES module)', done => {
+    const Foo = () => Promise.resolve({
+      __esModule: true,
+      default: {
+        render: h => h('div', [h('span', 'foo'), h(Bar)])
       }
-    }, result => {
-      expect(result).toContain('<span data-server-rendered="true" class="b">testAsync</span>')
+    })
+    const Bar = () => ({
+      component: Promise.resolve({
+        __esModule: true,
+        default: {
+          render: h => h('span', 'bar')
+        }
+      })
+    })
+    renderVmWithOptions({
+      render: h => h(Foo)
+    }, res => {
+      expect(res).toContain(`<div data-server-rendered="true"><span>foo</span><span>bar</span></div>`)
       done()
     })
   })
 
-  it('renders nested asynchronous component', done => {
+  it('renders async component (hoc)', done => {
     renderVmWithOptions({
-      template: `
-        <div>
-          <test-async></test-async>
-        </div>
-      `,
+      template: '<test-async></test-async>',
       components: {
-        testAsync (resolve) {
-          const options = {
-            template: `
-              <span class="b">
-                <test-sub-async></test-sub-async>
-              </span>
-            `
-          }
-
-          options.components = {
-            testSubAsync (resolve) {
-              resolve({
-                render () {
-                  return this.$createElement('div', { class: ['c'] }, 'testSubAsync')
-                }
-              })
-            }
+        testAsync: () => Promise.resolve({
+          render () {
+            return this.$createElement('span', { class: ['b'] }, 'testAsync')
           }
-          resolve(options)
-        }
+        })
       }
     }, result => {
-      expect(result).toContain('<div data-server-rendered="true"><span class="b"><div class="c">testSubAsync</div></span></div>')
+      expect(result).toContain('<span data-server-rendered="true" class="b">testAsync</span>')
       done()
     })
   })
@@ -877,23 +880,6 @@ describe('SSR: renderToString', () => {
       done()
     })
   })
-
-  it('render async components', done => {
-    const Foo = () => Promise.resolve({
-      render: h => h('div', [h('span', 'foo'), h(Bar)])
-    })
-    const Bar = () => ({
-      component: Promise.resolve({
-        render: h => h('span', 'bar')
-      })
-    })
-    renderVmWithOptions({
-      render: h => h(Foo)
-    }, res => {
-      expect(res).toContain(`<div data-server-rendered="true"><span>foo</span><span>bar</span></div>`)
-      done()
-    })
-  })
 })
 
 function renderVmWithOptions (options, cb) {

+ 27 - 0
test/unit/features/component/component-async.spec.js

@@ -26,6 +26,33 @@ describe('Component async', () => {
     }
   })
 
+  it('resolve ES module default', done => {
+    const vm = new Vue({
+      template: '<div><test></test></div>',
+      components: {
+        test: (resolve) => {
+          setTimeout(() => {
+            resolve({
+              __esModule: true,
+              default: {
+                template: '<div>hi</div>'
+              }
+            })
+            // wait for parent update
+            Vue.nextTick(next)
+          }, 0)
+        }
+      }
+    }).$mount()
+    expect(vm.$el.innerHTML).toBe('<!---->')
+    expect(vm.$children.length).toBe(0)
+    function next () {
+      expect(vm.$el.innerHTML).toBe('<div>hi</div>')
+      expect(vm.$children.length).toBe(1)
+      done()
+    }
+  })
+
   it('as root', done => {
     const vm = new Vue({
       template: '<test></test>',