Przeglądaj źródła

feat(ssr): renderToString return Promise

close #6160
Evan You 8 lat temu
rodzic
commit
f881dd175a

+ 10 - 1
src/server/bundle-renderer/create-bundle-renderer.js

@@ -1,5 +1,6 @@
 /* @flow */
 
+import { createPromiseCallback } from '../util'
 import { createBundleRunner } from './create-bundle-runner'
 import type { Renderer, RenderOptions } from '../create-renderer'
 import { createSourceMapConsumers, rewriteErrorTrace } from './source-map-support'
@@ -85,11 +86,17 @@ export function createBundleRendererCreator (
     )
 
     return {
-      renderToString: (context?: Object, cb: (err: ?Error, res: ?string) => void) => {
+      renderToString: (context?: Object, cb: any) => {
         if (typeof context === 'function') {
           cb = context
           context = {}
         }
+
+        let promise
+        if (!cb) {
+          ({ promise, cb } = createPromiseCallback())
+        }
+
         run(context).catch(err => {
           rewriteErrorTrace(err, maps)
           cb(err)
@@ -101,6 +108,8 @@ export function createBundleRendererCreator (
             })
           }
         })
+
+        return promise
       },
 
       renderToStream: (context?: Object) => {

+ 17 - 7
src/server/create-renderer.js

@@ -3,11 +3,12 @@
 import RenderStream from './render-stream'
 import { createWriteFunction } from './write'
 import { createRenderFunction } from './render'
+import { createPromiseCallback } from './util'
 import TemplateRenderer from './template-renderer/index'
 import type { ClientManifest } from './template-renderer/index'
 
 export type Renderer = {
-  renderToString: (component: Component, context: any, cb: any) => void;
+  renderToString: (component: Component, context: any, cb: any) => ?Promise<string>;
   renderToStream: (component: Component, context?: Object) => stream$Readable;
 };
 
@@ -52,30 +53,39 @@ export function createRenderer ({
     renderToString (
       component: Component,
       context: any,
-      done: any
-    ): void {
+      cb: any
+    ): ?Promise<string> {
       if (typeof context === 'function') {
-        done = context
+        cb = context
         context = {}
       }
       if (context) {
         templateRenderer.bindRenderFns(context)
       }
+
+      // no callback, return Promise
+      let promise
+      if (!cb) {
+        ({ promise, cb } = createPromiseCallback())
+      }
+
       let result = ''
       const write = createWriteFunction(text => {
         result += text
         return false
-      }, done)
+      }, cb)
       try {
         render(component, write, context, () => {
           if (template) {
             result = templateRenderer.renderSync(result, context)
           }
-          done(null, result)
+          cb(null, result)
         })
       } catch (e) {
-        done(e)
+        cb(e)
       }
+
+      return promise
     },
 
     renderToStream (

+ 13 - 0
src/server/util.js

@@ -3,3 +3,16 @@
 export const isJS = (file: string): boolean => /\.js(\?[^.]+)?$/.test(file)
 
 export const isCSS = (file: string): boolean => /\.css(\?[^.]+)?$/.test(file)
+
+export function createPromiseCallback () {
+  let resolve, reject
+  const promise: Promise<string> = new Promise((_resolve, _reject) => {
+    resolve = _resolve
+    reject = _reject
+  })
+  const cb = (err: Error, res?: string) => {
+    if (err) return reject(err)
+    resolve(res || '')
+  }
+  return { promise, cb }
+}

+ 30 - 1
test/ssr/ssr-bundle-render.spec.js

@@ -95,7 +95,7 @@ function createAssertions (runInNewContext) {
   })
 
   it('renderToStream catch Promise rejection', done => {
-    createRenderer('error.js', { runInNewContext }, renderer => {
+    createRenderer('promise-rejection.js', { runInNewContext }, renderer => {
       const stream = renderer.renderToStream()
       stream.on('error', err => {
         expect(err.message).toBe('foo')
@@ -277,4 +277,33 @@ function createAssertions (runInNewContext) {
       })
     })
   })
+
+  it('renderToString return Promise', done => {
+    createRenderer('app.js', { runInNewContext }, renderer => {
+      const context = { url: '/test' }
+      renderer.renderToString(context).then(res => {
+        expect(res).toBe('<div data-server-rendered="true">/test</div>')
+        expect(context.msg).toBe('hello')
+        done()
+      })
+    })
+  })
+
+  it('renderToString return Promise (error)', done => {
+    createRenderer('error.js', { runInNewContext }, renderer => {
+      renderer.renderToString().catch(err => {
+        expect(err.message).toBe('foo')
+        done()
+      })
+    })
+  })
+
+  it('renderToString return Promise (Promise rejection)', done => {
+    createRenderer('promise-rejection.js', { runInNewContext }, renderer => {
+      renderer.renderToString().catch(err => {
+        expect(err.message).toBe('foo')
+        done()
+      })
+    })
+  })
 }

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

@@ -957,6 +957,29 @@ describe('SSR: renderToString', () => {
       done()
     })
   })
+
+  it('return Promise', done => {
+    renderToString(new Vue({
+      template: `<div>{{ foo }}</div>`,
+      data: { foo: 'bar' }
+    })).then(res => {
+      expect(res).toBe(`<div data-server-rendered="true">bar</div>`)
+      done()
+    })
+  })
+
+  it('should Promise (error)', done => {
+    Vue.config.silent = true
+    renderToString(new Vue({
+      render () {
+        throw new Error('foobar')
+      }
+    })).catch(err => {
+      expect(err.toString()).toContain(`foobar`)
+      Vue.config.silent = false
+      done()
+    })
+  })
 })
 
 function renderVmWithOptions (options, cb) {