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

fix(ssr): fix SSR for async functional components

fix #7784
Evan You 8 лет назад
Родитель
Сommit
882e7199fd
3 измененных файлов с 79 добавлено и 4 удалено
  1. 6 1
      src/server/render-context.js
  2. 19 3
      src/server/render.js
  3. 54 0
      test/ssr/ssr-string.spec.js

+ 6 - 1
src/server/render-context.js

@@ -67,13 +67,18 @@ export class RenderContext {
     }
     switch (lastState.type) {
       case 'Element':
+      case 'Fragment':
         const { children, total } = lastState
         const rendered = lastState.rendered++
         if (rendered < total) {
           this.renderNode(children[rendered], false, this)
         } else {
           this.renderStates.pop()
-          this.write(lastState.endTag, this.next)
+          if (lastState.endTag) {
+            this.write(lastState.endTag, this.next)
+          } else {
+            this.next()
+          }
         }
         break
       case 'Component':

+ 19 - 3
src/server/render.js

@@ -191,7 +191,21 @@ function renderAsyncComponent (node, isRoot, context) {
       tag
     )
     if (resolvedNode) {
-      renderComponent(resolvedNode, isRoot, context)
+      if (resolvedNode.componnetInstance) {
+        renderComponent(resolvedNode, isRoot, context)
+      } else if (!Array.isArray(resolvedNode)) {
+        // single return node from functional component
+        renderNode(resolvedNode, isRoot, context)
+      } else {
+        // multiple return nodes from functional component
+        context.renderStates.push({
+          type: 'Fragment',
+          children: resolvedNode,
+          rendered: 0,
+          total: resolvedNode.length
+        })
+        context.next()
+      }
     } else {
       // invalid component, but this does not throw on the client
       // so render empty comment node
@@ -232,9 +246,10 @@ function renderStringNode (el, context) {
     const children: Array<VNode> = el.children
     context.renderStates.push({
       type: 'Element',
+      children,
       rendered: 0,
       total: children.length,
-      endTag: el.close, children
+      endTag: el.close
     })
     write(el.open, next)
   }
@@ -263,9 +278,10 @@ function renderElement (el, isRoot, context) {
     const children: Array<VNode> = el.children
     context.renderStates.push({
       type: 'Element',
+      children,
       rendered: 0,
       total: children.length,
-      endTag, children
+      endTag
     })
     write(startTag, next)
   }

+ 54 - 0
test/ssr/ssr-string.spec.js

@@ -575,6 +575,60 @@ describe('SSR: renderToString', () => {
     })
   })
 
+  it('renders async component (functional, single node)', done => {
+    renderVmWithOptions({
+      template: `
+        <div>
+          <test-async></test-async>
+        </div>
+      `,
+      components: {
+        testAsync (resolve) {
+          setTimeout(() => resolve({
+            functional: true,
+            render (h) {
+              return h('span', { class: ['b'] }, 'testAsync')
+            }
+          }), 1)
+        }
+      }
+    }, result => {
+      expect(result).toContain('<div data-server-rendered="true"><span class="b">testAsync</span></div>')
+      done()
+    })
+  })
+
+  it('renders async component (functional, multiple nodes)', done => {
+    renderVmWithOptions({
+      template: `
+        <div>
+          <test-async></test-async>
+        </div>
+      `,
+      components: {
+        testAsync (resolve) {
+          setTimeout(() => resolve({
+            functional: true,
+            render (h) {
+              return [
+                h('span', { class: ['a'] }, 'foo'),
+                h('span', { class: ['b'] }, 'bar')
+              ]
+            }
+          }), 1)
+        }
+      }
+    }, result => {
+      expect(result).toContain(
+        '<div data-server-rendered="true">' +
+          '<span class="a">foo</span>' +
+          '<span class="b">bar</span>' +
+        '</div>'
+      )
+      done()
+    })
+  })
+
   it('should catch async component error', done => {
     Vue.config.silent = true
     renderToString(new Vue({