Browse Source

expose preload/prefetch/scripts rendering on render context if no template is provided

Evan You 9 years ago
parent
commit
78b73686ea

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

@@ -40,7 +40,7 @@ export function createRenderer ({
   clientManifest
   clientManifest
 }: RenderOptions = {}): Renderer {
 }: RenderOptions = {}): Renderer {
   const render = createRenderFunction(modules, directives, isUnaryTag, cache)
   const render = createRenderFunction(modules, directives, isUnaryTag, cache)
-  const templateRenderer = template && new TemplateRenderer({
+  const templateRenderer = new TemplateRenderer({
     template,
     template,
     shouldPreload,
     shouldPreload,
     serverManifest,
     serverManifest,
@@ -53,13 +53,16 @@ export function createRenderer ({
       done: (err: ?Error, res: ?string) => any,
       done: (err: ?Error, res: ?string) => any,
       context?: ?Object
       context?: ?Object
     ): void {
     ): void {
+      if (!template && context && clientManifest) {
+        exposeAssetRenderFns(context, templateRenderer)
+      }
       let result = ''
       let result = ''
       const write = createWriteFunction(text => {
       const write = createWriteFunction(text => {
         result += text
         result += text
       }, done)
       }, done)
       try {
       try {
         render(component, write, () => {
         render(component, write, () => {
-          if (templateRenderer) {
+          if (template) {
             result = templateRenderer.renderSync(result, context)
             result = templateRenderer.renderSync(result, context)
           }
           }
           done(null, result)
           done(null, result)
@@ -76,7 +79,10 @@ export function createRenderer ({
       const renderStream = new RenderStream((write, done) => {
       const renderStream = new RenderStream((write, done) => {
         render(component, write, done)
         render(component, write, done)
       })
       })
-      if (!templateRenderer) {
+      if (!template) {
+        if (context && clientManifest) {
+          exposeAssetRenderFns(context, templateRenderer)
+        }
         return renderStream
         return renderStream
       } else {
       } else {
         const templateStream = templateRenderer.createStream(context)
         const templateStream = templateRenderer.createStream(context)
@@ -89,3 +95,11 @@ export function createRenderer ({
     }
     }
   }
   }
 }
 }
+
+// Expose preload/prefetch and script render fns when client manifest is
+// available.
+function exposeAssetRenderFns (context: Object, renderer: TemplateRenderer) {
+  context.renderPreloadLinks = renderer.renderPreloadLinks.bind(renderer, context)
+  context.renderPrefetchLinks = renderer.renderPrefetchLinks.bind(renderer, context)
+  context.renderScripts = renderer.renderScripts.bind(renderer, context)
+}

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

@@ -13,7 +13,7 @@ const JS_RE = /\.js($|\?)/
 export const isJS = (file: string): boolean => JS_RE.test(file)
 export const isJS = (file: string): boolean => JS_RE.test(file)
 
 
 type TemplateRendererOptions = {
 type TemplateRendererOptions = {
-  template: string;
+  template: ?string;
   serverManifest?: ServerManifest;
   serverManifest?: ServerManifest;
   clientManifest?: ClientManifest;
   clientManifest?: ClientManifest;
   shouldPreload?: (file: string, type: string) => boolean;
   shouldPreload?: (file: string, type: string) => boolean;
@@ -40,7 +40,7 @@ export type ClientManifest = {
 
 
 export default class TemplateRenderer {
 export default class TemplateRenderer {
   options: TemplateRendererOptions;
   options: TemplateRendererOptions;
-  template: ParsedTemplate;
+  parsedTemplate: ParsedTemplate | null;
   publicPath: string;
   publicPath: string;
   serverManifest: ServerManifest;
   serverManifest: ServerManifest;
   clientManifest: ClientManifest;
   clientManifest: ClientManifest;
@@ -50,7 +50,11 @@ export default class TemplateRenderer {
 
 
   constructor (options: TemplateRendererOptions) {
   constructor (options: TemplateRendererOptions) {
     this.options = options
     this.options = options
-    this.template = parseTemplate(options.template)
+    // 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)
+      : null
 
 
     // extra functionality with client manifest
     // extra functionality with client manifest
     if (options.serverManifest && options.clientManifest) {
     if (options.serverManifest && options.clientManifest) {
@@ -67,7 +71,10 @@ export default class TemplateRenderer {
 
 
   // render synchronously given rendered app content and render context
   // render synchronously given rendered app content and render context
   renderSync (content: string, context: ?Object) {
   renderSync (content: string, context: ?Object) {
-    const template = this.template
+    const template = this.parsedTemplate
+    if (!template) {
+      throw new Error('renderSync cannot be called without a template.')
+    }
     context = context || {}
     context = context || {}
     return (
     return (
       template.head +
       template.head +
@@ -178,7 +185,10 @@ export default class TemplateRenderer {
 
 
   // create a transform stream
   // create a transform stream
   createStream (context: ?Object): TemplateStream {
   createStream (context: ?Object): TemplateStream {
-    return new TemplateStream(this, context || {})
+    if (!this.parsedTemplate) {
+      throw new Error('createStream cannot be called without a template.')
+    }
+    return new TemplateStream(this, this.parsedTemplate, context || {})
   }
   }
 }
 }
 
 

+ 6 - 2
src/server/template-renderer/template-stream.js

@@ -10,11 +10,15 @@ export default class TemplateStream extends Transform {
   template: ParsedTemplate;
   template: ParsedTemplate;
   context: Object;
   context: Object;
 
 
-  constructor (renderer: TemplateRenderer, context: Object) {
+  constructor (
+    renderer: TemplateRenderer,
+    template: ParsedTemplate,
+    context: Object
+  ) {
     super()
     super()
     this.started = false
     this.started = false
     this.renderer = renderer
     this.renderer = renderer
-    this.template = renderer.template
+    this.template = template
     this.context = context || {}
     this.context = context || {}
   }
   }
 
 

+ 39 - 11
test/ssr/ssr-template.spec.js

@@ -25,14 +25,17 @@ function generateClientManifest (file, cb) {
   })
   })
 }
 }
 
 
-function createRendererWithManifest (file, cb, shouldPreload) {
+function createRendererWithManifest (file, options, cb) {
+  if (typeof options === 'function') {
+    cb = options
+    options = null
+  }
   generateClientManifest(file, clientManifest => {
   generateClientManifest(file, clientManifest => {
-    createBundleRenderer(file, {
+    createBundleRenderer(file, Object.assign({
       asBundle: true,
       asBundle: true,
       template: defaultTemplate,
       template: defaultTemplate,
-      clientManifest,
-      shouldPreload
-    }, cb)
+      clientManifest
+    }, options), cb)
   })
   })
 }
 }
 
 
@@ -176,8 +179,14 @@ describe('SSR: template option', () => {
     })
     })
   })
   })
 
 
-  it('bundleRenderer + renderToStream + clientManifest', done => {
-    createRendererWithManifest('split.js', renderer => {
+  it('bundleRenderer + renderToStream + clientManifest + shouldPreload', done => {
+    createRendererWithManifest('split.js', {
+      shouldPreload: (file, type) => {
+        if (type === 'image' || type === 'script' || type === 'font') {
+          return true
+        }
+      }
+    }, renderer => {
       const stream = renderer.renderToStream({})
       const stream = renderer.renderToStream({})
       let res = ''
       let res = ''
       stream.on('data', chunk => {
       stream.on('data', chunk => {
@@ -187,10 +196,29 @@ describe('SSR: template option', () => {
         expect(res).toContain(expectedHTMLWithManifest(true))
         expect(res).toContain(expectedHTMLWithManifest(true))
         done()
         done()
       })
       })
-    }, (file, type) => {
-      if (type === 'image' || type === 'script' || type === 'font') {
-        return true
-      }
+    })
+  })
+
+  it('bundleRenderer + renderToString + clientManifest + no template', done => {
+    createRendererWithManifest('split.js', {
+      template: null
+    }, renderer => {
+      const context = {}
+      renderer.renderToString(context, (err, res) => {
+        expect(err).toBeNull()
+
+        const customOutput =
+          `<html><head>${
+            context.renderPreloadLinks() +
+            context.renderPrefetchLinks()
+          }</head><body>${
+            res +
+            context.renderScripts()
+          }</body></html>`
+
+        expect(customOutput).toContain(expectedHTMLWithManifest(false))
+        done()
+      })
     })
     })
   })
   })
 })
 })