mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-27 08:02:01 +00:00
refactor: improve modern middleware and spa modern rendering (#5037)
This commit is contained in:
parent
04cdd60211
commit
05299d6738
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user