From 7bc899ce0ec10be3fbd4bd7e78b66dd357249c81 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 5 Oct 2017 23:08:09 -0400 Subject: [PATCH] feat(ssr): add shouldPrefetch option close #5964 --- src/server/create-renderer.js | 3 ++ src/server/template-renderer/index.js | 59 +++++++++++++++------------ test/ssr/ssr-template.spec.js | 25 +++++++++++- 3 files changed, 60 insertions(+), 27 deletions(-) diff --git a/src/server/create-renderer.js b/src/server/create-renderer.js index 4345f81264..8be2c44968 100644 --- a/src/server/create-renderer.js +++ b/src/server/create-renderer.js @@ -27,6 +27,7 @@ export type RenderOptions = { inject?: boolean; basedir?: string; shouldPreload?: Function; + shouldPrefetch?: Function; clientManifest?: ClientManifest; runInNewContext?: boolean | 'once'; }; @@ -39,6 +40,7 @@ export function createRenderer ({ inject, cache, shouldPreload, + shouldPrefetch, clientManifest }: RenderOptions = {}): Renderer { const render = createRenderFunction(modules, directives, isUnaryTag, cache) @@ -46,6 +48,7 @@ export function createRenderer ({ template, inject, shouldPreload, + shouldPrefetch, clientManifest }) diff --git a/src/server/template-renderer/index.js b/src/server/template-renderer/index.js index ac549b289d..48aa0caa68 100644 --- a/src/server/template-renderer/index.js +++ b/src/server/template-renderer/index.js @@ -15,6 +15,7 @@ type TemplateRendererOptions = { inject?: boolean; clientManifest?: ClientManifest; shouldPreload?: (file: string, type: string) => boolean; + shouldPrefetch?: (file: string, type: string) => boolean; }; export type ClientManifest = { @@ -30,7 +31,7 @@ export type ClientManifest = { } }; -type PreloadFile = { +type Resource = { file: string; extension: string; fileWithoutQuery: string; @@ -43,8 +44,8 @@ export default class TemplateRenderer { parsedTemplate: ParsedTemplate | null; publicPath: string; clientManifest: ClientManifest; - preloadFiles: Array; - prefetchFiles: Array; + preloadFiles: Array; + prefetchFiles: Array; mapFiles: AsyncFileMapper; constructor (options: TemplateRendererOptions) { @@ -61,8 +62,8 @@ export default class TemplateRenderer { const clientManifest = this.clientManifest = options.clientManifest this.publicPath = clientManifest.publicPath.replace(/\/$/, '') // preload/prefetch directives - this.preloadFiles = clientManifest.initial - this.prefetchFiles = clientManifest.async + this.preloadFiles = (clientManifest.initial || []).map(normalizeFile) + this.prefetchFiles = (clientManifest.async || []).map(normalizeFile) // initial async chunk mapping this.mapFiles = createMapper(clientManifest) } @@ -125,19 +126,10 @@ export default class TemplateRenderer { return this.renderPreloadLinks(context) + this.renderPrefetchLinks(context) } - getPreloadFiles (context: Object): Array { + getPreloadFiles (context: Object): Array { const usedAsyncFiles = this.getUsedAsyncFiles(context) if (this.preloadFiles || usedAsyncFiles) { - return (this.preloadFiles || []).concat(usedAsyncFiles || []).map(file => { - const withoutQuery = file.replace(/\?.*/, '') - const extension = path.extname(withoutQuery).slice(1) - return { - file, - extension, - fileWithoutQuery: withoutQuery, - asType: getPreloadType(extension) - } - }) + return (this.preloadFiles || []).concat(usedAsyncFiles || []) } else { return [] } @@ -145,10 +137,10 @@ export default class TemplateRenderer { renderPreloadLinks (context: Object): string { const files = this.getPreloadFiles(context) + const shouldPreload = this.options.shouldPreload if (files.length) { return files.map(({ file, extension, fileWithoutQuery, asType }) => { let extra = '' - const shouldPreload = this.options.shouldPreload // by default, we only preload scripts or css if (!shouldPreload && asType !== 'script' && asType !== 'style') { return '' @@ -174,17 +166,20 @@ export default class TemplateRenderer { } renderPrefetchLinks (context: Object): string { + const shouldPrefetch = this.options.shouldPrefetch if (this.prefetchFiles) { const usedAsyncFiles = this.getUsedAsyncFiles(context) const alreadyRendered = file => { - return usedAsyncFiles && usedAsyncFiles.some(f => f === file) + return usedAsyncFiles && usedAsyncFiles.some(f => f.file === file) } - return this.prefetchFiles.map(file => { - if (!alreadyRendered(file)) { - return `` - } else { + return this.prefetchFiles.map(({ file, fileWithoutQuery, asType }) => { + if (shouldPrefetch && !shouldPrefetch(fileWithoutQuery, asType)) { + return '' + } + if (alreadyRendered(file)) { return '' } + return `` }).join('') } else { return '' @@ -205,10 +200,10 @@ export default class TemplateRenderer { renderScripts (context: Object): string { if (this.clientManifest) { - const initial = this.clientManifest.initial + const initial = this.preloadFiles const async = this.getUsedAsyncFiles(context) const needed = [initial[0]].concat(async || [], initial.slice(1)) - return needed.filter(isJS).map(file => { + return needed.filter(({ file }) => isJS(file)).map(({ file }) => { return `` }).join('') } else { @@ -216,9 +211,10 @@ export default class TemplateRenderer { } } - getUsedAsyncFiles (context: Object): ?Array { + getUsedAsyncFiles (context: Object): ?Array { if (!context._mappedFiles && context._registeredComponents && this.mapFiles) { - context._mappedFiles = this.mapFiles(Array.from(context._registeredComponents)) + const registered = Array.from(context._registeredComponents) + context._mappedFiles = this.mapFiles(registered).map(normalizeFile) } return context._mappedFiles } @@ -232,6 +228,17 @@ export default class TemplateRenderer { } } +function normalizeFile (file: string): Resource { + const withoutQuery = file.replace(/\?.*/, '') + const extension = path.extname(withoutQuery).slice(1) + return { + file, + extension, + fileWithoutQuery: withoutQuery, + asType: getPreloadType(extension) + } +} + function getPreloadType (ext: string): string { if (ext === 'js') { return 'script' diff --git a/test/ssr/ssr-template.spec.js b/test/ssr/ssr-template.spec.js index c0a5fca5c7..ad7c041385 100644 --- a/test/ssr/ssr-template.spec.js +++ b/test/ssr/ssr-template.spec.js @@ -230,7 +230,7 @@ describe('SSR: template option', () => { (options.preloadOtherAssets ? `` : ``) + (options.preloadOtherAssets ? `` : ``) + // unused chunks should have prefetch - `` + + (options.noPrefetch ? `` : ``) + // css assets should be loaded `` + `` + @@ -281,6 +281,29 @@ describe('SSR: template option', () => { }) }) + it('bundleRenderer + renderToStream + clientManifest + shouldPrefetch', done => { + createRendererWithManifest('split.js', { + runInNewContext, + shouldPrefetch: (file, type) => { + if (type === 'script') { + return false + } + } + }, renderer => { + const stream = renderer.renderToStream({ state: { a: 1 }}) + let res = '' + stream.on('data', chunk => { + res += chunk.toString() + }) + stream.on('end', () => { + expect(res).toContain(expectedHTMLWithManifest({ + noPrefetch: true + })) + done() + }) + }) + }) + it('bundleRenderer + renderToString + clientManifest + inject: false', done => { createRendererWithManifest('split.js', { runInNewContext,