|
|
@@ -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
|
|
|
- }
|
|
|
-}
|