feat: preload and push modern resources in modern mode (#4362)

This commit is contained in:
Clark Du 2018-11-26 12:09:30 +00:00 committed by GitHub
parent 6f78bcdfea
commit 701190d796
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 75 additions and 19 deletions

View File

@ -51,7 +51,7 @@ export default class VueRenderer {
renderScripts(context) { renderScripts(context) {
if (this.context.options.modern === 'client') { if (this.context.options.modern === 'client') {
const publicPath = this.context.options.build.publicPath const publicPath = this.context.options.build.publicPath
const scriptPattern = /<script[^>]*?src="([^"]*)"[^>]*>[^<]*<\/script>/g const scriptPattern = /<script[^>]*?src="([^"]*?)"[^>]*?>[^<]*?<\/script>/g
return context.renderScripts().replace(scriptPattern, (scriptTag, jsFile) => { return context.renderScripts().replace(scriptPattern, (scriptTag, jsFile) => {
const legacyJsFile = jsFile.replace(publicPath, '') const legacyJsFile = jsFile.replace(publicPath, '')
const modernJsFile = this.assetsMapping[legacyJsFile] const modernJsFile = this.assetsMapping[legacyJsFile]
@ -63,17 +63,36 @@ export default class VueRenderer {
return context.renderScripts() return context.renderScripts()
} }
getModernFiles(legacyFiles = []) {
const modernFiles = []
for (const legacyJsFile of legacyFiles) {
const modernFile = { ...legacyJsFile }
if (modernFile.asType === 'script') {
const file = this.assetsMapping[legacyJsFile.file]
modernFile.file = file
modernFile.fileWithoutQuery = file.replace(/\?.*/, '')
}
modernFiles.push(modernFile)
}
return modernFiles
}
getPreloadFiles(context) {
const preloadFiles = context.getPreloadFiles()
const modernMode = this.context.options.modern
// In eligible server modern mode, preloadFiles are modern bundles from modern renderer
return modernMode === 'client' ? this.getModernFiles(preloadFiles) : preloadFiles
}
renderResourceHints(context) { renderResourceHints(context) {
if (this.context.options.modern === 'client') { if (this.context.options.modern === 'client') {
const modulePreloadTags = []
for (const legacyJsFile of context.getPreloadFiles()) {
if (legacyJsFile.asType === 'script') {
const publicPath = this.context.options.build.publicPath const publicPath = this.context.options.build.publicPath
const modernJsFile = this.assetsMapping[legacyJsFile.file] const linkPattern = /<link[^>]*?href="([^"]*?)"[^>]*?as="script"[^>]*?>/g
modulePreloadTags.push(`<link rel="modulepreload" href="${publicPath}${modernJsFile}" as="script">`) return context.renderResourceHints().replace(linkPattern, (linkTag, jsFile) => {
} const legacyJsFile = jsFile.replace(publicPath, '')
} const modernJsFile = this.assetsMapping[legacyJsFile]
return modulePreloadTags.join('') return linkTag.replace('rel="preload"', 'rel="modulepreload"').replace(legacyJsFile, modernJsFile)
})
} }
return context.renderResourceHints() return context.renderResourceHints()
} }
@ -262,7 +281,7 @@ export default class VueRenderer {
ENV ENV
}) })
return { html, getPreloadFiles } return { html, getPreloadFiles: this.getPreloadFiles.bind(this, { getPreloadFiles }) }
} }
let APP let APP
@ -322,7 +341,7 @@ export default class VueRenderer {
return { return {
html, html,
cspScriptSrcHashSet, cspScriptSrcHashSet,
getPreloadFiles: context.getPreloadFiles, getPreloadFiles: this.getPreloadFiles.bind(this, context),
error: context.nuxt.error, error: context.nuxt.error,
redirected: context.redirected redirected: context.redirected
} }

View File

@ -111,8 +111,8 @@ export default class SPAMetaRenderer {
// Emulate getPreloadFiles from vue-server-renderer (works for JS chunks only) // Emulate getPreloadFiles from vue-server-renderer (works for JS chunks only)
meta.getPreloadFiles = () => meta.getPreloadFiles = () =>
clientManifest.initial clientManifest.initial
.filter(file => shouldPreload(file))
.map(SPAMetaRenderer.normalizeFile) .map(SPAMetaRenderer.normalizeFile)
.filter(({ fileWithoutQuery, asType }) => shouldPreload(fileWithoutQuery, asType))
// Set meta tags inside cache // Set meta tags inside cache
this.cache.set(url, meta) this.cache.set(url, meta)

View File

@ -9,5 +9,10 @@ export default {
return `${isModern ? 'modern-' : ''}[name].js` return `${isModern ? 'modern-' : ''}[name].js`
} }
} }
},
render: {
http2: {
push: true
}
} }
} }

View File

@ -1,4 +1,4 @@
import { loadFixture, getPort, Nuxt, rp } from '../utils' import { loadFixture, getPort, Nuxt, rp, wChunk } from '../utils'
let nuxt, port let nuxt, port
const url = route => 'http://localhost:' + port + route const url = route => 'http://localhost:' + port + route
@ -29,6 +29,16 @@ describe('modern client mode', () => {
expect(response).toContain('<link rel="modulepreload" href="/_nuxt/modern-commons.app.js" as="script">') expect(response).toContain('<link rel="modulepreload" href="/_nuxt/modern-commons.app.js" as="script">')
}) })
test('should contain module http2 pushed resources', async () => {
const { headers: { link } } = await rp(url('/'), { resolveWithFullResponse: true })
expect(link).toEqual([
'</_nuxt/modern-runtime.js>; rel=preload; as=script',
'</_nuxt/modern-commons.app.js>; rel=preload; as=script',
'</_nuxt/modern-app.js>; rel=preload; as=script',
`</_nuxt/modern-${wChunk('pages/index.js')}>; rel=preload; as=script`
].join(', '))
})
// Close server and ask nuxt to stop listening to file changes // Close server and ask nuxt to stop listening to file changes
afterAll(async () => { afterAll(async () => {
await nuxt.close() await nuxt.close()

View File

@ -2,6 +2,7 @@ import { loadFixture, getPort, Nuxt, rp, wChunk } from '../utils'
let nuxt, port let nuxt, port
const url = route => 'http://localhost:' + port + route const url = route => 'http://localhost:' + port + route
const modernUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
describe('modern server mode', () => { describe('modern server mode', () => {
beforeAll(async () => { beforeAll(async () => {
@ -18,11 +19,7 @@ describe('modern server mode', () => {
}) })
test('should use modern resources for modern resources', async () => { test('should use modern resources for modern resources', async () => {
const response = await rp(url('/'), { const response = await rp(url('/'), { headers: { 'user-agent': modernUA } })
headers: {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
}
})
expect(response).toContain('/_nuxt/modern-app.js') expect(response).toContain('/_nuxt/modern-app.js')
expect(response).toContain('/_nuxt/modern-commons.app.js') expect(response).toContain('/_nuxt/modern-commons.app.js')
}) })
@ -37,6 +34,31 @@ describe('modern server mode', () => {
expect(response).toContain('arrow:function(){return"build test"}') expect(response).toContain('arrow:function(){return"build test"}')
}) })
test('should contain legacy http2 pushed resources', async () => {
const { headers: { link } } = await rp(url('/'), {
resolveWithFullResponse: true
})
expect(link).toEqual([
'</_nuxt/runtime.js>; rel=preload; as=script',
'</_nuxt/commons.app.js>; rel=preload; as=script',
'</_nuxt/app.js>; rel=preload; as=script',
`</_nuxt/${wChunk('pages/index.js')}>; rel=preload; as=script`
].join(', '))
})
test('should contain module http2 pushed resources', async () => {
const { headers: { link } } = await rp(url('/'), {
headers: { 'user-agent': modernUA },
resolveWithFullResponse: true
})
expect(link).toEqual([
'</_nuxt/modern-runtime.js>; rel=preload; as=script',
'</_nuxt/modern-commons.app.js>; rel=preload; as=script',
'</_nuxt/modern-app.js>; rel=preload; as=script',
`</_nuxt/modern-${wChunk('pages/index.js')}>; rel=preload; as=script`
].join(', '))
})
// Close server and ask nuxt to stop listening to file changes // Close server and ask nuxt to stop listening to file changes
afterAll(async () => { afterAll(async () => {
await nuxt.close() await nuxt.close()