refactor: improve modern middleware and spa modern rendering (#5037)

This commit is contained in:
Xin Du (Clark) 2019-03-03 08:52:59 +01:00 committed by Pooya Parsa
parent 04cdd60211
commit 05299d6738
5 changed files with 49 additions and 44 deletions

View File

@ -44,29 +44,18 @@ const detectModernBuild = ({ options, resources }) => {
}
const detectModernBrowser = ({ socket = {}, headers }) => {
if (socket.isModernBrowser !== undefined) {
return
if (socket.isModernBrowser === undefined) {
const ua = headers && headers['user-agent']
socket.isModernBrowser = isModernBrowser(ua)
}
const ua = headers && headers['user-agent']
socket.isModernBrowser = isModernBrowser(ua)
}
const setModernMode = (req, options) => {
const { socket: { isModernBrowser } = {} } = req
if (options.modern === 'server') {
req.modernMode = isModernBrowser
}
if (options.dev && !!options.modern) {
req.devModernMode = isModernBrowser
}
return socket.isModernBrowser
}
export default ({ context }) => (req, res, next) => {
detectModernBuild(context)
if (context.options.modern !== false) {
detectModernBrowser(req)
setModernMode(req, context.options)
req.modernMode = detectModernBrowser(req)
}
next()
}

View File

@ -88,7 +88,7 @@ export default class Server {
if (this.options.dev) {
this.useMiddleware(modernMiddleware)
this.useMiddleware(async (req, res, next) => {
const name = req.devModernMode ? 'modern' : 'client'
const name = req.modernMode ? 'modern' : 'client'
if (this.devMiddleware && this.devMiddleware[name]) {
await this.devMiddleware[name](req, res)
}

View File

@ -87,14 +87,13 @@ export default class VueRenderer {
return modernFiles
}
getPreloadFiles(context) {
getSsrPreloadFiles(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
return this.context.options.modern === 'client' ? this.getModernFiles(preloadFiles) : preloadFiles
}
renderResourceHints(context) {
renderSsrResourceHints(context) {
if (this.context.options.modern === 'client') {
const { publicPath, crossorigin } = this.context.options.build
const linkPattern = /<link[^>]*?href="([^"]*?)"[^>]*?as="script"[^>]*?>/g
@ -320,9 +319,7 @@ export default class VueRenderer {
return {
html,
getPreloadFiles: this.getPreloadFiles.bind(this, {
getPreloadFiles: content.getPreloadFiles
})
getPreloadFiles: content.getPreloadFiles
}
}
@ -363,7 +360,7 @@ export default class VueRenderer {
// Inject resource hints
if (this.context.options.render.resourceHints) {
HEAD += this.renderResourceHints(context)
HEAD += this.renderSsrResourceHints(context)
}
// Inject styles
@ -409,7 +406,7 @@ export default class VueRenderer {
return {
html,
cspScriptSrcHashes,
getPreloadFiles: this.getPreloadFiles.bind(this, context),
getPreloadFiles: this.getSsrPreloadFiles.bind(this, context),
error: context.nuxt.error,
redirected: context.redirected
}
@ -438,15 +435,17 @@ export default class VueRenderer {
// Add url to the context
context.url = url
const { req = {} } = context
// context.spa
if (context.spa === undefined) {
// TODO: Remove reading from context.res in Nuxt3
context.spa = !this.SSR || context.spa || (context.req && context.req.spa) || (context.res && context.res.spa)
context.spa = !this.SSR || context.spa || req.spa || (context.res && context.res.spa)
}
// context.modern
if (context.modern === undefined) {
context.modern = context.req ? (context.req.modernMode || context.req.modern) : false
context.modern = req.modernMode && this.context.options.modern === 'server'
}
// Call context hook

View File

@ -30,8 +30,9 @@ export default class SPAMetaRenderer {
return vm.$meta().inject()
}
async render({ url = '/' }) {
let meta = this.cache.get(url)
async render({ url = '/', req = {} }) {
const cacheKey = `${req.modernMode ? 'modern:' : 'legacy:'}${url}`
let meta = this.cache.get(cacheKey)
if (meta) {
return meta
@ -73,8 +74,8 @@ export default class SPAMetaRenderer {
meta.resourceHints = ''
const { modernManifest, clientManifest } = this.renderer.context.resources
const manifest = this.options.modern ? modernManifest : clientManifest
const { resources: { modernManifest, clientManifest } } = this.renderer.context
const manifest = req.modernMode ? modernManifest : clientManifest
const { shouldPreload, shouldPrefetch } = this.options.render.bundleRenderer
@ -86,15 +87,18 @@ export default class SPAMetaRenderer {
const { crossorigin } = this.options.build
const cors = `${crossorigin ? ` crossorigin="${crossorigin}"` : ''}`
meta.resourceHints += manifest.initial
meta.preloadFiles = manifest.initial
.map(SPAMetaRenderer.normalizeFile)
.filter(({ fileWithoutQuery, asType }) => shouldPreload(fileWithoutQuery, asType))
.map(({ file, extension, fileWithoutQuery, asType }) => {
.map(file => ({ ...file, modern: req.modernMode }))
meta.resourceHints += meta.preloadFiles
.map(({ file, extension, fileWithoutQuery, asType, modern }) => {
let extra = ''
if (asType === 'font') {
extra = ` type="font/${extension}" crossorigin`
extra = ` type="font/${extension}"${cors ? '' : ' crossorigin'}`
}
return `<link rel="${this.options.modern ? 'module' : ''}preload"${cors} href="${publicPath}${file}"${
return `<link rel="${modern ? 'module' : ''}preload"${cors} href="${publicPath}${file}"${
asType !== '' ? ` as="${asType}"` : ''}${extra}>`
})
.join('')
@ -116,13 +120,10 @@ export default class SPAMetaRenderer {
}
// Emulate getPreloadFiles from vue-server-renderer (works for JS chunks only)
meta.getPreloadFiles = () =>
clientManifest.initial
.map(SPAMetaRenderer.normalizeFile)
.filter(({ fileWithoutQuery, asType }) => shouldPreload(fileWithoutQuery, asType))
meta.getPreloadFiles = () => (meta.preloadFiles || [])
// Set meta tags inside cache
this.cache.set(url, meta)
this.cache.set(cacheKey, meta)
return meta
}

View File

@ -4,6 +4,7 @@ import { loadFixture, getPort, Nuxt, rp } from '../utils'
let nuxt, port, options
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'
const modernInfo = mode => `Modern bundles are detected. Modern mode (${chalk.green.bold(mode)}) is enabled now.`
describe('modern client mode (SPA)', () => {
@ -31,14 +32,29 @@ describe('modern client mode (SPA)', () => {
expect(response).toContain('<script type="module" src="/_nuxt/modern-commons.app.js" crossorigin="use-credentials"')
})
test('should contain module preload resources', async () => {
test('should contain legacy preload resources', async () => {
const response = await rp(url('/'))
expect(response).toContain('<link rel="preload" crossorigin="use-credentials" href="/_nuxt/app.js" as="script">')
expect(response).toContain('<link rel="preload" crossorigin="use-credentials" href="/_nuxt/commons.app.js" as="script">')
})
test('should contain legacy http2 pushed resources', async () => {
const { headers: { link } } = await rp(url('/'), { resolveWithFullResponse: true })
expect(link).toEqual([
'</_nuxt/runtime.js>; rel=preload; crossorigin=use-credentials; as=script',
'</_nuxt/commons.app.js>; rel=preload; crossorigin=use-credentials; as=script',
'</_nuxt/app.js>; rel=preload; crossorigin=use-credentials; as=script'
].join(', '))
})
test('should contain modern preload resources', async () => {
const response = await rp(url('/'), { headers: { 'user-agent': modernUA } })
expect(response).toContain('<link rel="modulepreload" crossorigin="use-credentials" href="/_nuxt/modern-app.js" as="script">')
expect(response).toContain('<link rel="modulepreload" crossorigin="use-credentials" href="/_nuxt/modern-commons.app.js" as="script">')
})
test('should contain module http2 pushed resources', async () => {
const { headers: { link } } = await rp(url('/'), { resolveWithFullResponse: true })
test('should contain modern http2 pushed resources', async () => {
const { headers: { link } } = await rp(url('/'), { headers: { 'user-agent': modernUA }, resolveWithFullResponse: true })
expect(link).toEqual([
'</_nuxt/modern-runtime.js>; rel=modulepreload; crossorigin=use-credentials; as=script',
'</_nuxt/modern-commons.app.js>; rel=modulepreload; crossorigin=use-credentials; as=script',