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

handle updated bundle format and client manfiest

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

+ 3 - 9
src/entries/web-server-renderer.js

@@ -12,20 +12,14 @@ export function createRenderer (options?: Object = {}): {
   renderToString: Function,
   renderToStream: Function
 } {
-  return _createRenderer({
+  return _createRenderer(Object.assign({}, options, {
     isUnaryTag,
     canBeLeftOpenTag,
     modules,
     // user can provide server-side implementations for custom directives
     // when creating the renderer.
-    directives: Object.assign(baseDirectives, options.directives),
-    // component cache (optional)
-    cache: options.cache,
-    // page template (optional)
-    template: options.template,
-    // server/client build manifests (optional)
-    manifest: options.manifest
-  })
+    directives: Object.assign(baseDirectives, options.directives)
+  }))
 }
 
 export const createBundleRenderer = createBundleRendererCreator(createRenderer)

+ 12 - 3
src/server/bundle-renderer/create-bundle-renderer.js

@@ -24,6 +24,7 @@ type RenderBundle = {
   entry: string;
   files: { [filename: string]: string; };
   maps: { [filename: string]: string; };
+  modules?: { [filename: string]: Array<string> };
 };
 
 export function createBundleRendererCreator (createRenderer: () => Renderer) {
@@ -31,9 +32,7 @@ export function createBundleRendererCreator (createRenderer: () => Renderer) {
     bundle: string | RenderBundle,
     rendererOptions?: RenderOptions
   ) {
-    const renderer = createRenderer(rendererOptions)
-
-    let files, entry, maps
+    let files, entry, maps, moduleMappings
     let basedir = rendererOptions && rendererOptions.basedir
 
     // load bundle if given filepath
@@ -63,6 +62,7 @@ export function createBundleRendererCreator (createRenderer: () => Renderer) {
       files = bundle.files
       basedir = basedir || bundle.basedir
       maps = createSourceMapConsumers(bundle.maps)
+      moduleMappings = bundle.modules
       if (typeof entry !== 'string' || typeof files !== 'object') {
         throw new Error(INVALID_MSG)
       }
@@ -74,6 +74,15 @@ export function createBundleRendererCreator (createRenderer: () => Renderer) {
       throw new Error(INVALID_MSG)
     }
 
+    if (moduleMappings) {
+      rendererOptions = Object.assign({}, rendererOptions, {
+        serverManifest: {
+          modules: moduleMappings
+        }
+      })
+    }
+    const renderer = createRenderer(rendererOptions)
+
     const run = createBundleRunner(entry, files, basedir)
 
     return {

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

@@ -4,6 +4,7 @@ import RenderStream from './render-stream'
 import TemplateRenderer from './template-renderer/index'
 import { createWriteFunction } from './write'
 import { createRenderFunction } from './render'
+import type { ClientManifest, ServerManifest } from './template-renderer/index'
 
 export type Renderer = {
   renderToString: (component: Component, cb: (err: ?Error, res: ?string) => void) => void;
@@ -23,10 +24,8 @@ export type RenderOptions = {
   cache?: RenderCache;
   template?: string;
   basedir?: string;
-  manifest?: {
-    server: Object;
-    client: Object;
-  }
+  serverManifest?: ServerManifest;
+  clientManifest?: ClientManifest;
 };
 
 export function createRenderer ({
@@ -35,12 +34,14 @@ export function createRenderer ({
   isUnaryTag = (() => false),
   template,
   cache,
-  manifest
+  serverManifest,
+  clientManifest
 }: RenderOptions = {}): Renderer {
   const render = createRenderFunction(modules, directives, isUnaryTag, cache)
   const templateRenderer = template && new TemplateRenderer({
     template,
-    manifest
+    serverManifest,
+    clientManifest
   })
 
   return {

+ 27 - 46
src/server/template-renderer/create-async-file-mapper.js

@@ -6,8 +6,16 @@
  * directly in the rendered HTML to avoid waterfall requests.
  */
 
-export function createMapper (serverStats: Object, clientStats: Object) {
-  const fileMap = createFileMap(serverStats, clientStats)
+import type { ServerManifest, ClientManifest } from './index'
+
+export type AsyncFileMapper = (files: Array<string>) => Array<string>;
+
+export function createMapper (
+  serverManifest: ServerManifest,
+  clientManifest: ClientManifest
+): AsyncFileMapper {
+  const fileMap = createFileMap(serverManifest, clientManifest)
+
   return function mapFiles (files: Array<string>): Array<string> {
     const res = new Set()
     for (let i = 0; i < files.length; i++) {
@@ -22,54 +30,27 @@ export function createMapper (serverStats: Object, clientStats: Object) {
   }
 }
 
-function createFileMap (serverStats, clientStats) {
+function createFileMap (serverManifest, clientManifest) {
   const fileMap = new Map()
-  serverStats.assets
-    .filter(asset => /\.js$/.test(asset.name))
-    .forEach(asset => {
-      const mapped = mapFile(asset.name, serverStats, clientStats)
-      fileMap.set(asset.name, mapped)
-    })
+  Object.keys(serverManifest.modules).forEach(file => {
+    fileMap.set(file, mapFile(serverManifest.modules[file], clientManifest))
+  })
   return fileMap
 }
 
-function mapFile (file, serverStats, clientStats) {
-  // 1. server file -> server chunk ids
-  const serverChunkIds = new Set()
-  const asset = serverStats.assets.find(asset => asset.name === file)
-  if (!asset) return []
-  asset.chunks.forEach(id => {
-    const chunk = serverStats.chunks.find(c => c.id === id)
-    if (!chunk.initial) { // only map async chunks
-      serverChunkIds.add(id)
-    }
-  })
-
-  // 2. server chunk ids -> module identifiers
-  const moduleIdentifiers = []
-  serverStats.modules.forEach(module => {
-    if (module.chunks.some(id => serverChunkIds.has(id))) {
-      moduleIdentifiers.push(module.identifier)
-    }
-  })
-
-  // 3. module identifiers -> client chunk ids
-  const clientChunkIds = new Set()
-  moduleIdentifiers.forEach(identifier => {
-    const clientModule = clientStats.modules.find(m => m.identifier === identifier)
-    if (clientModule && clientModule.chunks.length === 1) { // ignore modules duplicated in multiple chunks
-      clientChunkIds.add(clientModule.chunks[0])
-    }
-  })
-
-  // 4. client chunks -> client files
-  const clientFiles = new Set()
-  Array.from(clientChunkIds).forEach(id => {
-    const chunk = clientStats.chunks.find(chunk => chunk.id === id)
-    if (!chunk.initial) {
-      chunk.files.forEach(file => clientFiles.add(file))
+function mapFile (moduleIds, clientManifest) {
+  const files = new Set()
+  moduleIds.forEach(id => {
+    const fileIndices = clientManifest.modules[id]
+    if (fileIndices) {
+      fileIndices.forEach(index => {
+        const file = clientManifest.all[index]
+        // only include async files
+        if (clientManifest.async.indexOf(file) > -1) {
+          files.add(file)
+        }
+      })
     }
   })
-
-  return Array.from(clientFiles)
+  return Array.from(files)
 }

+ 35 - 78
src/server/template-renderer/index.js

@@ -1,65 +1,60 @@
 /* @flow */
 
 // TODO: handle <script> tag embedding (so we can get rid of html-webpack-plugin)
-// TODO: remove need for separate server manifest (should be included in bundle)
+
+const serialize = require('serialize-javascript')
 
 import TemplateStream from './template-stream'
+import { parseTemplate } from './parse-template'
 import { createMapper } from './create-async-file-mapper'
-
-const serialize = require('serialize-javascript')
+import type { ParsedTemplate } from './parse-template'
+import type { AsyncFileMapper } from './create-async-file-mapper'
 
 type TemplateRendererOptions = {
   template: string;
-  manifest?: {
-    server: Object;
-    client: Object;
+  serverManifest?: ServerManifest;
+  clientManifest?: ClientManifest;
+};
+
+export type ServerManifest = {
+  modules: {
+    [file: string]: Array<string>;
   }
 };
 
-export type ParsedTemplate = {
-  head: string;
-  neck: string;
-  waist: string;
-  tail: string;
+export type ClientManifest = {
+  publicPath: string;
+  all: Array<string>;
+  initial: Array<string>;
+  async: Array<string>;
+  modules: {
+    [id: string]: Array<number>;
+  },
+  hasNoCssVersion?: {
+    [file: string]: boolean;
+  }
 };
 
 export default class TemplateRenderer {
   template: ParsedTemplate;
   publicPath: string;
-  clientManifest: Object;
+  serverManifest: ServerManifest;
+  clientManifest: ClientManifest;
   preloadFiles: ?Array<string>;
   prefetchFiles: ?Array<string>;
-  mapFiles: ?(files: Array<string>) => Array<string>;
+  mapFiles: ?AsyncFileMapper;
 
   constructor (options: TemplateRendererOptions) {
     this.template = parseTemplate(options.template)
 
-    // extra functionality with manifests
-    if (options.manifest) {
-      const serverManifest = options.manifest.server
-      const clientManifest = options.manifest.client
-      if (!serverManifest || !clientManifest) {
-        throw new Error(
-          'The manifest option must provide both server and client manifests.'
-        )
-      }
-
-      this.clientManifest = clientManifest
+    // extra functionality with client manifest
+    if (options.serverManifest && options.clientManifest) {
+      const serverManifest = this.serverManifest = options.serverManifest
+      const clientManifest = this.clientManifest = options.clientManifest
       this.publicPath = clientManifest.publicPath.replace(/\/$/, '')
-
       // preload/prefetch drectives
-      const clientInitialFiles = this.preloadFiles = []
-      const clientAsyncFiles = this.prefetchFiles = []
-      clientManifest.chunks.forEach(chunk => {
-        chunk.files.forEach(file => {
-          if (chunk.initial) {
-            clientInitialFiles.push(file)
-          } else {
-            clientAsyncFiles.push(file)
-          }
-        })
-      })
-
+      this.preloadFiles = clientManifest.initial
+      this.prefetchFiles = clientManifest.async
       // initial async chunk mapping
       this.mapFiles = createMapper(serverManifest, clientManifest)
     }
@@ -141,9 +136,10 @@ export default class TemplateRenderer {
       let mapped = this.mapFiles(Object.keys(context._evaluatedFiles))
       // if a file has a no-css version (produced by vue-ssr-webpack-plugin),
       // we should use that instead.
-      if (this.clientManifest && this.clientManifest.hasNoCssVersion) {
+      const noCssHash = this.clientManifest && this.clientManifest.hasNoCssVersion
+      if (noCssHash) {
         mapped = mapped.map(file => {
-          return this.clientManifest.hasNoCssVersion[file]
+          return noCssHash[file]
             ? file.replace(/\.js$/, '.no-css.js')
             : file
         })
@@ -157,42 +153,3 @@ export default class TemplateRenderer {
     return new TemplateStream(this, context || {})
   }
 }
-
-function parseTemplate (
-  template: string,
-  contentPlaceholder?: string = '<!--vue-ssr-outlet-->'
-): ParsedTemplate {
-  if (typeof template === 'object') {
-    return template
-  }
-
-  let i = template.indexOf('</head>')
-  const j = template.indexOf(contentPlaceholder)
-
-  if (j < 0) {
-    throw new Error(`Content placeholder not found in template.`)
-  }
-
-  if (i < 0) {
-    i = template.indexOf('<body>')
-    if (i < 0) {
-      i = j
-    }
-  }
-
-  let waist = ''
-  let tail = template.slice(j + contentPlaceholder.length)
-  let k = tail.indexOf('</script>')
-  if (k > 0) {
-    k += '</script>'.length
-    waist = tail.slice(0, k)
-    tail = tail.slice(k)
-  }
-
-  return {
-    head: template.slice(0, i),
-    neck: template.slice(i, j),
-    waist,
-    tail
-  }
-}

+ 47 - 0
src/server/template-renderer/parse-template.js

@@ -0,0 +1,47 @@
+/* @flow */
+
+export type ParsedTemplate = {
+  head: string;
+  neck: string;
+  waist: string;
+  tail: string;
+};
+
+export function parseTemplate (
+  template: string,
+  contentPlaceholder?: string = '<!--vue-ssr-outlet-->'
+): ParsedTemplate {
+  if (typeof template === 'object') {
+    return template
+  }
+
+  let i = template.indexOf('</head>')
+  const j = template.indexOf(contentPlaceholder)
+
+  if (j < 0) {
+    throw new Error(`Content placeholder not found in template.`)
+  }
+
+  if (i < 0) {
+    i = template.indexOf('<body>')
+    if (i < 0) {
+      i = j
+    }
+  }
+
+  let waist = ''
+  let tail = template.slice(j + contentPlaceholder.length)
+  let k = tail.indexOf('</script>')
+  if (k > 0) {
+    k += '</script>'.length
+    waist = tail.slice(0, k)
+    tail = tail.slice(k)
+  }
+
+  return {
+    head: template.slice(0, i),
+    neck: template.slice(i, j),
+    waist,
+    tail
+  }
+}

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

@@ -1,7 +1,8 @@
 /* @flow */
 
 const Transform = require('stream').Transform
-import type TemplateRenderer, { ParsedTemplate } from './index'
+import type TemplateRenderer from './index'
+import type { ParsedTemplate } from './parse-template'
 
 export default class TemplateStream extends Transform {
   started: boolean;