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

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

@@ -23,6 +23,7 @@ export type RenderOptions = {
   isUnaryTag?: Function;
   cache?: RenderCache;
   template?: string;
+  inject?: boolean;
   basedir?: string;
   shouldPreload?: Function;
   clientManifest?: ClientManifest;
@@ -34,6 +35,7 @@ export function createRenderer ({
   directives = {},
   isUnaryTag = (() => false),
   template,
+  inject,
   cache,
   shouldPreload,
   clientManifest
@@ -41,6 +43,7 @@ export function createRenderer ({
   const render = createRenderFunction(modules, directives, isUnaryTag, cache)
   const templateRenderer = new TemplateRenderer({
     template,
+    inject,
     shouldPreload,
     clientManifest
   })

+ 23 - 11
src/server/template-renderer/index.js

@@ -14,6 +14,7 @@ export const isJS = (file: string): boolean => JS_RE.test(file)
 
 type TemplateRendererOptions = {
   template: ?string;
+  inject?: boolean;
   clientManifest?: ClientManifest;
   shouldPreload?: (file: string, type: string) => boolean;
 };
@@ -33,6 +34,7 @@ export type ClientManifest = {
 
 export default class TemplateRenderer {
   options: TemplateRendererOptions;
+  inject: boolean;
   parsedTemplate: ParsedTemplate | null;
   publicPath: string;
   clientManifest: ClientManifest;
@@ -42,6 +44,7 @@ export default class TemplateRenderer {
 
   constructor (options: TemplateRendererOptions) {
     this.options = options
+    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
@@ -74,17 +77,26 @@ export default class TemplateRenderer {
       throw new Error('renderSync cannot be called without a template.')
     }
     context = context || {}
-    return (
-      template.head(context) +
-      (context.head || '') +
-      this.renderLinks(context) +
-      this.renderStyles(context) +
-      template.neck(context) +
-      content +
-      this.renderState(context) +
-      this.renderScripts(context) +
-      template.tail(context)
-    )
+    if (this.inject) {
+      return (
+        template.head(context) +
+        (context.head || '') +
+        this.renderLinks(context) +
+        this.renderStyles(context) +
+        template.neck(context) +
+        content +
+        this.renderState(context) +
+        this.renderScripts(context) +
+        template.tail(context)
+      )
+    } else {
+      return (
+        template.head(context) +
+        template.neck(context) +
+        content +
+        template.tail(context)
+      )
+    }
   }
 
   renderStyles (context: Object): string {

+ 27 - 27
src/server/template-renderer/template-stream.js

@@ -9,6 +9,7 @@ export default class TemplateStream extends Transform {
   renderer: TemplateRenderer;
   template: ParsedTemplate;
   context: Object;
+  inject: boolean;
 
   constructor (
     renderer: TemplateRenderer,
@@ -20,6 +21,7 @@ export default class TemplateStream extends Transform {
     this.renderer = renderer
     this.template = template
     this.context = context || {}
+    this.inject = renderer.inject
   }
 
   _transform (data: Buffer | string, encoding: string, done: Function) {
@@ -35,26 +37,22 @@ export default class TemplateStream extends Transform {
     this.started = true
     this.push(this.template.head(this.context))
 
-    // inline server-rendered head meta information
-    if (this.context.head) {
-      this.push(this.context.head)
-    }
+    if (this.inject) {
+      // inline server-rendered head meta information
+      if (this.context.head) {
+        this.push(this.context.head)
+      }
 
-    // inline preload directives for initial chunks
-    const preloadLinks = this.renderer.renderPreloadLinks(this.context)
-    if (preloadLinks) {
-      this.push(preloadLinks)
-    }
+      // inline preload/prefetch directives for initial/async chunks
+      const links = this.renderer.renderLinks(this.context)
+      if (links) {
+        this.push(links)
+      }
 
-    // inline prefetch directives for async chunks not used during render
-    const prefetchLinks = this.renderer.renderPrefetchLinks(this.context)
-    if (prefetchLinks) {
-      this.push(prefetchLinks)
-    }
-
-    // inline server-rendered CSS collected by vue-style-loader
-    if (this.context.styles) {
-      this.push(this.context.styles)
+      // inline server-rendered CSS collected by vue-style-loader
+      if (this.context.styles) {
+        this.push(this.context.styles)
+      }
     }
 
     this.push(this.template.neck(this.context))
@@ -63,16 +61,18 @@ export default class TemplateStream extends Transform {
   _flush (done: Function) {
     this.emit('beforeEnd')
 
-    // inline initial store state
-    const state = this.renderer.renderState(this.context)
-    if (state) {
-      this.push(state)
-    }
+    if (this.inject) {
+      // inline initial store state
+      const state = this.renderer.renderState(this.context)
+      if (state) {
+        this.push(state)
+      }
 
-    // embed scripts needed
-    const scripts = this.renderer.renderScripts(this.context)
-    if (scripts) {
-      this.push(scripts)
+      // embed scripts needed
+      const scripts = this.renderer.renderScripts(this.context)
+      if (scripts) {
+        this.push(scripts)
+      }
     }
 
     this.push(this.template.tail(this.context))

+ 89 - 2
test/ssr/ssr-template.spec.js

@@ -6,6 +6,7 @@ import VueSSRClientPlugin from '../../packages/vue-server-renderer/client-plugin
 import { createRenderer as createBundleRenderer } from './ssr-bundle-render.spec.js'
 
 const defaultTemplate = `<html><head></head><body><!--vue-ssr-outlet--></body></html>`
+const interpolateTemplate = `<html><head><title>{{ title }}</title></head><body><!--vue-ssr-outlet-->{{{ snippet }}}</body></html>`
 
 function generateClientManifest (file, cb) {
   compileWithWebpack(file, {
@@ -65,6 +66,38 @@ describe('SSR: template option', () => {
     }, context)
   })
 
+  it('renderToString with interpolation', done => {
+    const renderer = createRenderer({
+      template: interpolateTemplate
+    })
+
+    const context = {
+      title: '<script>hacks</script>',
+      snippet: '<div>foo</div>',
+      head: '<meta name="viewport" content="width=device-width">',
+      styles: '<style>h1 { color: red }</style>',
+      state: { a: 1 }
+    }
+
+    renderer.renderToString(new Vue({
+      template: '<div>hi</div>'
+    }), (err, res) => {
+      expect(err).toBeNull()
+      expect(res).toContain(
+        `<html><head>` +
+        // double mustache should be escaped
+        `<title>&lt;script&gt;hacks&lt;/script&gt;</title>` +
+        `${context.head}${context.styles}</head><body>` +
+        `<div data-server-rendered="true">hi</div>` +
+        `<script>window.__INITIAL_STATE__={"a":1}</script>` +
+        // triple should be raw
+        `<div>foo</div>` +
+        `</body></html>`
+      )
+      done()
+    }, context)
+  })
+
   it('renderToStream', done => {
     const renderer = createRenderer({
       template: defaultTemplate
@@ -95,6 +128,43 @@ describe('SSR: template option', () => {
     })
   })
 
+  it('renderToStream with interpolation', done => {
+    const renderer = createRenderer({
+      template: interpolateTemplate
+    })
+
+    const context = {
+      title: '<script>hacks</script>',
+      snippet: '<div>foo</div>',
+      head: '<meta name="viewport" content="width=device-width">',
+      styles: '<style>h1 { color: red }</style>',
+      state: { a: 1 }
+    }
+
+    const stream = renderer.renderToStream(new Vue({
+      template: '<div>hi</div>'
+    }), context)
+
+    let res = ''
+    stream.on('data', chunk => {
+      res += chunk
+    })
+    stream.on('end', () => {
+      expect(res).toContain(
+        `<html><head>` +
+        // double mustache should be escaped
+        `<title>&lt;script&gt;hacks&lt;/script&gt;</title>` +
+        `${context.head}${context.styles}</head><body>` +
+        `<div data-server-rendered="true">hi</div>` +
+        `<script>window.__INITIAL_STATE__={"a":1}</script>` +
+        // triple should be raw
+        `<div>foo</div>` +
+        `</body></html>`
+      )
+      done()
+    })
+  })
+
   it('bundleRenderer + renderToString', done => {
     createBundleRenderer('app.js', {
       asBundle: true,
@@ -204,6 +274,24 @@ describe('SSR: template option', () => {
       })
     })
 
+    it('bundleRenderer + renderToString + clientManifest + inject: false', done => {
+      createRendererWithManifest('split.js', {
+        runInNewContext,
+        template: `<html>` +
+          `<head>{{{ renderLinks() }}}</head>` +
+          `<body><!--vue-ssr-outlet-->{{{ renderScripts() }}}</body>` +
+        `</html>`,
+        inject: false
+      }, renderer => {
+        const context = {}
+        renderer.renderToString(context, (err, res) => {
+          expect(err).toBeNull()
+          expect(res).toContain(expectedHTMLWithManifest(false))
+          done()
+        })
+      })
+    })
+
     it('bundleRenderer + renderToString + clientManifest + no template', done => {
       createRendererWithManifest('split.js', {
         runInNewContext,
@@ -215,8 +303,7 @@ describe('SSR: template option', () => {
 
           const customOutput =
             `<html><head>${
-              context.renderPreloadLinks() +
-              context.renderPrefetchLinks()
+              context.renderLinks()
             }</head><body>${
               res +
               context.renderScripts()