diff --git a/packages/config/src/config/render.js b/packages/config/src/config/render.js index 508197b549..15d82a5b53 100644 --- a/packages/config/src/config/render.js +++ b/packages/config/src/config/render.js @@ -2,7 +2,8 @@ export default () => ({ bundleRenderer: { - shouldPrefetch: () => false + shouldPrefetch: () => false, + shouldPreload: (fileWithoutQuery, asType) => ['script', 'style'].includes(asType) }, resourceHints: true, ssr: undefined, diff --git a/packages/vue-renderer/src/spa-meta.js b/packages/vue-renderer/src/spa-meta.js index 4940477085..512eb08d4e 100644 --- a/packages/vue-renderer/src/spa-meta.js +++ b/packages/vue-renderer/src/spa-meta.js @@ -1,3 +1,4 @@ +import { extname } from 'path' import Vue from 'vue' import VueMeta from 'vue-meta' import { createRenderer } from 'vue-server-renderer' @@ -70,8 +71,8 @@ export default class SPAMetaRenderer { const clientManifest = this.renderer.context.resources.clientManifest - const shouldPreload = this.options.render.bundleRenderer.shouldPreload || (() => true) - const shouldPrefetch = this.options.render.bundleRenderer.shouldPrefetch || (() => true) + const shouldPreload = this.options.render.bundleRenderer.shouldPreload + const shouldPrefetch = this.options.render.bundleRenderer.shouldPrefetch if (this.options.render.resourceHints && clientManifest) { const publicPath = clientManifest.publicPath || '/_nuxt/' @@ -79,18 +80,25 @@ export default class SPAMetaRenderer { // Preload initial resources if (Array.isArray(clientManifest.initial)) { meta.resourceHints += clientManifest.initial - .filter(file => shouldPreload(file)) - .map( - r => `` - ) + .map(SPAMetaRenderer.normalizeFile) + .filter(({ fileWithoutQuery, asType }) => shouldPreload(fileWithoutQuery, asType)) + .map(({ file, extension, fileWithoutQuery, asType }) => { + let extra = '' + if (asType === 'font') { + extra = ` type="font/${extension}" crossorigin` + } + return `` + }) .join('') } // Prefetch async resources if (Array.isArray(clientManifest.async)) { meta.resourceHints += clientManifest.async - .filter(file => shouldPrefetch(file)) - .map(r => ``) + .map(SPAMetaRenderer.normalizeFile) + .filter(({ fileWithoutQuery, asType }) => shouldPrefetch(fileWithoutQuery, asType)) + .map(({ file }) => ``) .join('') } @@ -104,16 +112,36 @@ export default class SPAMetaRenderer { meta.getPreloadFiles = () => clientManifest.initial .filter(file => shouldPreload(file)) - .map(r => ({ - file: r, - fileWithoutQuery: r, - asType: 'script', - extension: 'js' - })) + .map(SPAMetaRenderer.normalizeFile) // Set meta tags inside cache this.cache.set(url, meta) return meta } + + static normalizeFile(file) { + const withoutQuery = file.replace(/\?.*/, '') + const extension = extname(withoutQuery).slice(1) + return { + file, + extension, + fileWithoutQuery: withoutQuery, + asType: SPAMetaRenderer.getPreloadType(extension) + } + } + + static getPreloadType(ext) { + if (ext === 'js') { + return 'script' + } else if (ext === 'css') { + return 'style' + } else if (/jpe?g|png|svg|gif|webp|ico/.test(ext)) { + return 'image' + } else if (/woff2?|ttf|otf|eot/.test(ext)) { + return 'font' + } else { + return '' + } + } } diff --git a/test/fixtures/spa/nuxt.config.js b/test/fixtures/spa/nuxt.config.js index d8337c48e8..e3f902b0f4 100644 --- a/test/fixtures/spa/nuxt.config.js +++ b/test/fixtures/spa/nuxt.config.js @@ -4,6 +4,15 @@ export default { render: { http2: { push: true + }, + bundleRenderer: { + shouldPrefetch: () => true + } + }, + build: { + filenames: { + app: '[name].js', + chunk: '[name].js' } }, plugins: [ diff --git a/test/unit/spa.test.js b/test/unit/spa.test.js index 41dfa70b41..34dd665e59 100644 --- a/test/unit/spa.test.js +++ b/test/unit/spa.test.js @@ -1,5 +1,5 @@ import consola from 'consola' -import { loadFixture, getPort, Nuxt } from '../utils' +import { loadFixture, getPort, Nuxt, wChunk } from '../utils' let nuxt, port const url = route => 'http://localhost:' + port + route @@ -27,6 +27,21 @@ describe('spa', () => { consola.log.mockClear() }) + test('/ (include preload and prefetch resources)', async () => { + const { head } = await renderRoute('/') + expect(head).toMatch(``) + expect(head).toMatch(``) + expect(head).toMatch(``) + expect(head).toMatch(``) + expect(head).toMatch(``) + expect(head).toMatch(``) + expect(head).toMatch(``) + expect(head).toMatch(``) + expect(head).toMatch(``) + expect(head).toMatch(``) + consola.log.mockClear() + }) + test('/custom (custom layout)', async () => { const { html } = await renderRoute('/custom') expect(html).toMatch('Custom layout')