refactor: spa renderer (#4316)

This commit is contained in:
Clark Du 2018-11-21 13:08:03 +00:00 committed by Pooya Parsa
parent d590858ff5
commit 70f0dc3825
4 changed files with 69 additions and 16 deletions

View File

@ -2,7 +2,8 @@
export default () => ({ export default () => ({
bundleRenderer: { bundleRenderer: {
shouldPrefetch: () => false shouldPrefetch: () => false,
shouldPreload: (fileWithoutQuery, asType) => ['script', 'style'].includes(asType)
}, },
resourceHints: true, resourceHints: true,
ssr: undefined, ssr: undefined,

View File

@ -1,3 +1,4 @@
import { extname } from 'path'
import Vue from 'vue' import Vue from 'vue'
import VueMeta from 'vue-meta' import VueMeta from 'vue-meta'
import { createRenderer } from 'vue-server-renderer' import { createRenderer } from 'vue-server-renderer'
@ -70,8 +71,8 @@ export default class SPAMetaRenderer {
const clientManifest = this.renderer.context.resources.clientManifest const clientManifest = this.renderer.context.resources.clientManifest
const shouldPreload = this.options.render.bundleRenderer.shouldPreload || (() => true) const shouldPreload = this.options.render.bundleRenderer.shouldPreload
const shouldPrefetch = this.options.render.bundleRenderer.shouldPrefetch || (() => true) const shouldPrefetch = this.options.render.bundleRenderer.shouldPrefetch
if (this.options.render.resourceHints && clientManifest) { if (this.options.render.resourceHints && clientManifest) {
const publicPath = clientManifest.publicPath || '/_nuxt/' const publicPath = clientManifest.publicPath || '/_nuxt/'
@ -79,18 +80,25 @@ export default class SPAMetaRenderer {
// Preload initial resources // Preload initial resources
if (Array.isArray(clientManifest.initial)) { if (Array.isArray(clientManifest.initial)) {
meta.resourceHints += clientManifest.initial meta.resourceHints += clientManifest.initial
.filter(file => shouldPreload(file)) .map(SPAMetaRenderer.normalizeFile)
.map( .filter(({ fileWithoutQuery, asType }) => shouldPreload(fileWithoutQuery, asType))
r => `<link rel="preload" href="${publicPath}${r}" as="script" />` .map(({ file, extension, fileWithoutQuery, asType }) => {
) let extra = ''
if (asType === 'font') {
extra = ` type="font/${extension}" crossorigin`
}
return `<link rel="preload" href="${publicPath}${file}"${
asType !== '' ? ` as="${asType}"` : ''}${extra}>`
})
.join('') .join('')
} }
// Prefetch async resources // Prefetch async resources
if (Array.isArray(clientManifest.async)) { if (Array.isArray(clientManifest.async)) {
meta.resourceHints += clientManifest.async meta.resourceHints += clientManifest.async
.filter(file => shouldPrefetch(file)) .map(SPAMetaRenderer.normalizeFile)
.map(r => `<link rel="prefetch" href="${publicPath}${r}" />`) .filter(({ fileWithoutQuery, asType }) => shouldPrefetch(fileWithoutQuery, asType))
.map(({ file }) => `<link rel="prefetch" href="${publicPath}${file}">`)
.join('') .join('')
} }
@ -104,16 +112,36 @@ export default class SPAMetaRenderer {
meta.getPreloadFiles = () => meta.getPreloadFiles = () =>
clientManifest.initial clientManifest.initial
.filter(file => shouldPreload(file)) .filter(file => shouldPreload(file))
.map(r => ({ .map(SPAMetaRenderer.normalizeFile)
file: r,
fileWithoutQuery: r,
asType: 'script',
extension: 'js'
}))
// Set meta tags inside cache // Set meta tags inside cache
this.cache.set(url, meta) this.cache.set(url, meta)
return 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 ''
}
}
} }

View File

@ -4,6 +4,15 @@ export default {
render: { render: {
http2: { http2: {
push: true push: true
},
bundleRenderer: {
shouldPrefetch: () => true
}
},
build: {
filenames: {
app: '[name].js',
chunk: '[name].js'
} }
}, },
plugins: [ plugins: [

View File

@ -1,5 +1,5 @@
import consola from 'consola' import consola from 'consola'
import { loadFixture, getPort, Nuxt } from '../utils' import { loadFixture, getPort, Nuxt, wChunk } from '../utils'
let nuxt, port let nuxt, port
const url = route => 'http://localhost:' + port + route const url = route => 'http://localhost:' + port + route
@ -27,6 +27,21 @@ describe('spa', () => {
consola.log.mockClear() consola.log.mockClear()
}) })
test('/ (include preload and prefetch resources)', async () => {
const { head } = await renderRoute('/')
expect(head).toMatch(`<link rel="preload" href="/_nuxt/runtime.js" as="script">`)
expect(head).toMatch(`<link rel="preload" href="/_nuxt/commons.app.js" as="script">`)
expect(head).toMatch(`<link rel="preload" href="/_nuxt/app.js" as="script">`)
expect(head).toMatch(`<link rel="prefetch" href="/_nuxt/${wChunk('pages/custom.js')}">`)
expect(head).toMatch(`<link rel="prefetch" href="/_nuxt/${wChunk('pages/error-handler-async.js')}">`)
expect(head).toMatch(`<link rel="prefetch" href="/_nuxt/${wChunk('pages/error-handler-object.js')}">`)
expect(head).toMatch(`<link rel="prefetch" href="/_nuxt/${wChunk('pages/error-handler-string.js')}">`)
expect(head).toMatch(`<link rel="prefetch" href="/_nuxt/${wChunk('pages/error-handler.js')}">`)
expect(head).toMatch(`<link rel="prefetch" href="/_nuxt/${wChunk('pages/index.js')}">`)
expect(head).toMatch(`<link rel="prefetch" href="/_nuxt/${wChunk('pages/mounted.js')}">`)
consola.log.mockClear()
})
test('/custom (custom layout)', async () => { test('/custom (custom layout)', async () => {
const { html } = await renderRoute('/custom') const { html } = await renderRoute('/custom')
expect(html).toMatch('Custom layout') expect(html).toMatch('Custom layout')