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 }) => { const detectModernBrowser = ({ socket = {}, headers }) => {
if (socket.isModernBrowser !== undefined) { if (socket.isModernBrowser === undefined) {
return const ua = headers && headers['user-agent']
socket.isModernBrowser = isModernBrowser(ua)
} }
const ua = headers && headers['user-agent'] return socket.isModernBrowser
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
}
} }
export default ({ context }) => (req, res, next) => { export default ({ context }) => (req, res, next) => {
detectModernBuild(context) detectModernBuild(context)
if (context.options.modern !== false) { if (context.options.modern !== false) {
detectModernBrowser(req) req.modernMode = detectModernBrowser(req)
setModernMode(req, context.options)
} }
next() next()
} }

View File

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

View File

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

View File

@ -30,8 +30,9 @@ export default class SPAMetaRenderer {
return vm.$meta().inject() return vm.$meta().inject()
} }
async render({ url = '/' }) { async render({ url = '/', req = {} }) {
let meta = this.cache.get(url) const cacheKey = `${req.modernMode ? 'modern:' : 'legacy:'}${url}`
let meta = this.cache.get(cacheKey)
if (meta) { if (meta) {
return meta return meta
@ -73,8 +74,8 @@ export default class SPAMetaRenderer {
meta.resourceHints = '' meta.resourceHints = ''
const { modernManifest, clientManifest } = this.renderer.context.resources const { resources: { modernManifest, clientManifest } } = this.renderer.context
const manifest = this.options.modern ? modernManifest : clientManifest const manifest = req.modernMode ? modernManifest : clientManifest
const { shouldPreload, shouldPrefetch } = this.options.render.bundleRenderer const { shouldPreload, shouldPrefetch } = this.options.render.bundleRenderer
@ -86,15 +87,18 @@ export default class SPAMetaRenderer {
const { crossorigin } = this.options.build const { crossorigin } = this.options.build
const cors = `${crossorigin ? ` crossorigin="${crossorigin}"` : ''}` const cors = `${crossorigin ? ` crossorigin="${crossorigin}"` : ''}`
meta.resourceHints += manifest.initial meta.preloadFiles = manifest.initial
.map(SPAMetaRenderer.normalizeFile) .map(SPAMetaRenderer.normalizeFile)
.filter(({ fileWithoutQuery, asType }) => shouldPreload(fileWithoutQuery, asType)) .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 = '' let extra = ''
if (asType === 'font') { 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}>` asType !== '' ? ` as="${asType}"` : ''}${extra}>`
}) })
.join('') .join('')
@ -116,13 +120,10 @@ 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 = () => (meta.preloadFiles || [])
clientManifest.initial
.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(cacheKey, meta)
return meta return meta
} }

View File

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