Sfoglia il codice sorgente

feat(ssr): add shouldPrefetch option

close #5964
Evan You 8 anni fa
parent
commit
7bc899ce0e

+ 3 - 0
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
   })
 

+ 33 - 26
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<string>;
-  prefetchFiles: Array<string>;
+  preloadFiles: Array<Resource>;
+  prefetchFiles: Array<Resource>;
   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<PreloadFile> {
+  getPreloadFiles (context: Object): Array<Resource> {
     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 `<link rel="prefetch" href="${this.publicPath}/${file}">`
-        } else {
+      return this.prefetchFiles.map(({ file, fileWithoutQuery, asType }) => {
+        if (shouldPrefetch && !shouldPrefetch(fileWithoutQuery, asType)) {
+          return ''
+        }
+        if (alreadyRendered(file)) {
           return ''
         }
+        return `<link rel="prefetch" href="${this.publicPath}/${file}">`
       }).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 `<script src="${this.publicPath}/${file}" defer></script>`
       }).join('')
     } else {
@@ -216,9 +211,10 @@ export default class TemplateRenderer {
     }
   }
 
-  getUsedAsyncFiles (context: Object): ?Array<string> {
+  getUsedAsyncFiles (context: Object): ?Array<Resource> {
     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'

+ 24 - 1
test/ssr/ssr-template.spec.js

@@ -230,7 +230,7 @@ describe('SSR: template option', () => {
       (options.preloadOtherAssets ? `<link rel="preload" href="/test.png" as="image">` : ``) +
       (options.preloadOtherAssets ? `<link rel="preload" href="/test.woff2" as="font" type="font/woff2" crossorigin>` : ``) +
       // unused chunks should have prefetch
-      `<link rel="prefetch" href="/1.js">` +
+      (options.noPrefetch ? `` : `<link rel="prefetch" href="/1.js">`) +
       // css assets should be loaded
       `<link rel="stylesheet" href="/test.css">` +
     `</head><body>` +
@@ -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,