Prechádzať zdrojové kódy

feat(ssr): allow template option to be function in renderToString (#9324)

Also allows return Promise for async handling
DominusVilicus 7 rokov pred
rodič
commit
b65f6d78e0

+ 19 - 5
src/server/create-renderer.js

@@ -23,7 +23,7 @@ export type RenderOptions = {
   directives?: Object;
   isUnaryTag?: Function;
   cache?: RenderCache;
-  template?: string;
+  template?: string | (content: string, context: any) => string;
   inject?: boolean;
   basedir?: string;
   shouldPreload?: Function;
@@ -82,14 +82,26 @@ export function createRenderer ({
       }, cb)
       try {
         render(component, write, context, err => {
+          if (err) {
+            return cb(err)
+          }
           if (context && context.rendered) {
             context.rendered(context)
           }
           if (template) {
-            result = templateRenderer.renderSync(result, context)
-          }
-          if (err) {
-            cb(err)
+            try {
+              const res = templateRenderer.render(result, context)
+              if (typeof res !== 'string') {
+                // function template returning promise
+                res
+                  .then(html => cb(null, html))
+                  .catch(cb)
+              } else {
+                cb(null, res)
+              }
+            } catch (e) {
+              cb(e)
+            }
           } else {
             cb(null, result)
           }
@@ -119,6 +131,8 @@ export function createRenderer ({
           })
         }
         return renderStream
+      } else if (typeof template === 'function') {
+        throw new Error(`function template is only supported in renderToString.`)
       } else {
         const templateStream = templateRenderer.createStream(context)
         renderStream.on('error', err => {

+ 15 - 6
src/server/template-renderer/index.js

@@ -11,7 +11,7 @@ import type { ParsedTemplate } from './parse-template'
 import type { AsyncFileMapper } from './create-async-file-mapper'
 
 type TemplateRendererOptions = {
-  template: ?string;
+  template?: string | (content: string, context: any) => string;
   inject?: boolean;
   clientManifest?: ClientManifest;
   shouldPreload?: (file: string, type: string) => boolean;
@@ -42,7 +42,7 @@ type Resource = {
 export default class TemplateRenderer {
   options: TemplateRendererOptions;
   inject: boolean;
-  parsedTemplate: ParsedTemplate | null;
+  parsedTemplate: ParsedTemplate | Function | null;
   publicPath: string;
   clientManifest: ClientManifest;
   preloadFiles: Array<Resource>;
@@ -55,8 +55,12 @@ export default class TemplateRenderer {
     this.inject = options.inject !== false
     // if no template option is provided, the renderer is created
     // as a utility object for rendering assets like preload links and scripts.
-    this.parsedTemplate = options.template
-      ? parseTemplate(options.template)
+    
+    const { template } = options
+    this.parsedTemplate = template
+      ? typeof template === 'string'
+        ? parseTemplate(template)
+        : template
       : null
 
     // function used to serialize initial state JSON
@@ -89,12 +93,17 @@ export default class TemplateRenderer {
   }
 
   // render synchronously given rendered app content and render context
-  renderSync (content: string, context: ?Object) {
+  render (content: string, context: ?Object): string | Promise<string> {
     const template = this.parsedTemplate
     if (!template) {
-      throw new Error('renderSync cannot be called without a template.')
+      throw new Error('render cannot be called without a template.')
     }
     context = context || {}
+
+    if (typeof template === 'function') {
+      return template(content, context)
+    }
+
     if (this.inject) {
       return (
         template.head(context) +