refactor: move modern detection from server to utils (#5584)

This commit is contained in:
Xin Du (Clark) 2019-04-23 10:16:56 +01:00 committed by Pooya Parsa
parent 912ef25fce
commit 31a15559e5
13 changed files with 103 additions and 178 deletions

View File

@ -22,11 +22,9 @@
"launch-editor-middleware": "^2.2.1", "launch-editor-middleware": "^2.2.1",
"on-headers": "^1.0.2", "on-headers": "^1.0.2",
"pify": "^4.0.1", "pify": "^4.0.1",
"semver": "^6.0.0",
"serve-placeholder": "^1.2.1", "serve-placeholder": "^1.2.1",
"serve-static": "^1.13.2", "serve-static": "^1.13.2",
"server-destroy": "^1.0.1", "server-destroy": "^1.0.1"
"ua-parser-js": "^0.7.19"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

View File

@ -1,13 +0,0 @@
export default {
Edge: '16',
Firefox: '60',
Chrome: '61',
'Chrome Headless': '61',
Chromium: '61',
Iron: '61',
Safari: '10.1',
Opera: '48',
Yandex: '18',
Vivaldi: '1.14',
'Mobile Safari': '10.3'
}

View File

@ -11,7 +11,6 @@ import renderAndGetWindow from './jsdom'
import nuxtMiddleware from './middleware/nuxt' import nuxtMiddleware from './middleware/nuxt'
import errorMiddleware from './middleware/error' import errorMiddleware from './middleware/error'
import Listener from './listener' import Listener from './listener'
import createModernMiddleware from './middleware/modern'
import createTimingMiddleware from './middleware/timing' import createTimingMiddleware from './middleware/timing'
export default class Server { export default class Server {
@ -113,11 +112,6 @@ export default class Server {
// Dev middleware // Dev middleware
if (this.options.dev) { if (this.options.dev) {
// devMiddleware needs req._modern for serving different files
this.useMiddleware(createModernMiddleware({
serverContext: this.serverContext
}))
this.useMiddleware((req, res, next) => { this.useMiddleware((req, res, next) => {
if (!this.devMiddleware) { if (!this.devMiddleware) {
return next() return next()
@ -159,13 +153,6 @@ export default class Server {
} }
} }
if (!this.options.dev) {
// Put detection after serve-static for avoiding unnecessary detections
this.useMiddleware(createModernMiddleware({
serverContext: this.serverContext
}))
}
// Finally use nuxtMiddleware // Finally use nuxtMiddleware
this.useMiddleware(nuxtMiddleware({ this.useMiddleware(nuxtMiddleware({
options: this.options, options: this.options,

View File

@ -1,107 +0,0 @@
jest.mock('chalk', () => ({
green: {
bold: modern => `greenBold(${modern})`
}
}))
const createServerContext = () => ({
resources: {},
options: {
render: {}
}
})
const createRenderContext = () => ({
req: { headers: {} },
next: jest.fn()
})
describe('server: modernMiddleware', () => {
let createModernMiddleware
beforeEach(() => {
jest.isolateModules(() => {
createModernMiddleware = require('../../src/middleware/modern').default
})
jest.clearAllMocks()
})
test('should return modern middleware', () => {
const modernMiddleware = createModernMiddleware({})
expect(modernMiddleware).toBeInstanceOf(Function)
})
test('should not detect modern build if modern mode is specified', () => {
const serverContext = createServerContext()
const modernMiddleware = createModernMiddleware({ serverContext })
const ctx = createRenderContext()
serverContext.options.modern = false
modernMiddleware(ctx.req, ctx.res, ctx.next)
serverContext.options.modern = 'client'
modernMiddleware(ctx.req, ctx.res, ctx.next)
serverContext.options.modern = 'server'
modernMiddleware(ctx.req, ctx.res, ctx.next)
expect(ctx.req._modern).toEqual(false)
})
test('should not detect modern browser if connect has been detected', () => {
const serverContext = createServerContext()
const modernMiddleware = createModernMiddleware({ serverContext })
const ctx = createRenderContext()
ctx.req.socket = { isModernBrowser: true }
serverContext.options.dev = true
serverContext.options.modern = 'server'
modernMiddleware(ctx.req, ctx.res, ctx.next)
expect(ctx.req._modern).toEqual(true)
})
test('should detect modern browser based on user-agent', () => {
const serverContext = createServerContext()
const modernMiddleware = createModernMiddleware({ serverContext })
const ctx = createRenderContext()
const ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
ctx.req.headers['user-agent'] = ua
ctx.req.socket = {}
serverContext.options.dev = true
serverContext.options.modern = 'server'
modernMiddleware(ctx.req, ctx.res, ctx.next)
expect(ctx.req.socket.isModernBrowser).toEqual(true)
expect(ctx.req._modern).toEqual(true)
})
test('should detect legacy browser based on user-agent', () => {
const serverContext = createServerContext()
const modernMiddleware = createModernMiddleware({ serverContext })
const ctx = createRenderContext()
const ua = 'Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US))'
ctx.req.headers['user-agent'] = ua
ctx.req.socket = {}
serverContext.options.dev = true
serverContext.options.modern = 'client'
modernMiddleware(ctx.req, ctx.res, ctx.next)
expect(ctx.req.socket.isModernBrowser).toEqual(false)
})
test('should ignore illegal user-agent', () => {
const serverContext = createServerContext()
const modernMiddleware = createModernMiddleware({ serverContext })
const ctx = createRenderContext()
const ua = 'illegal user agent'
ctx.req.headers['user-agent'] = ua
ctx.req.socket = {}
serverContext.options.dev = true
serverContext.options.modern = 'client'
modernMiddleware(ctx.req, ctx.res, ctx.next)
expect(ctx.req.socket.isModernBrowser).toEqual(false)
})
})

View File

@ -13,7 +13,6 @@ import ServerContext from '../src/context'
import renderAndGetWindow from '../src/jsdom' import renderAndGetWindow from '../src/jsdom'
import nuxtMiddleware from '../src/middleware/nuxt' import nuxtMiddleware from '../src/middleware/nuxt'
import errorMiddleware from '../src/middleware/error' import errorMiddleware from '../src/middleware/error'
import createModernMiddleware from '../src/middleware/modern'
import createTimingMiddleware from '../src/middleware/timing' import createTimingMiddleware from '../src/middleware/timing'
jest.mock('connect') jest.mock('connect')
@ -27,7 +26,6 @@ jest.mock('../src/context')
jest.mock('../src/jsdom') jest.mock('../src/jsdom')
jest.mock('../src/middleware/nuxt') jest.mock('../src/middleware/nuxt')
jest.mock('../src/middleware/error') jest.mock('../src/middleware/error')
jest.mock('../src/middleware/modern')
jest.mock('../src/middleware/timing') jest.mock('../src/middleware/timing')
describe('server: server', () => { describe('server: server', () => {
@ -72,10 +70,6 @@ describe('server: server', () => {
jest.spyOn(path, 'resolve').mockImplementation((...args) => `resolve(${args.join(', ')})`) jest.spyOn(path, 'resolve').mockImplementation((...args) => `resolve(${args.join(', ')})`)
connect.mockReturnValue({ use: jest.fn() }) connect.mockReturnValue({ use: jest.fn() })
serveStatic.mockImplementation(dir => ({ id: 'test-serve-static', dir })) serveStatic.mockImplementation(dir => ({ id: 'test-serve-static', dir }))
createModernMiddleware.mockImplementation(options => ({
id: 'test-modern-middleware',
...options
}))
nuxtMiddleware.mockImplementation(options => ({ nuxtMiddleware.mockImplementation(options => ({
id: 'test-nuxt-middleware', id: 'test-nuxt-middleware',
...options ...options
@ -178,7 +172,7 @@ describe('server: server', () => {
expect(server.nuxt.callHook).nthCalledWith(1, 'render:setupMiddleware', server.app) expect(server.nuxt.callHook).nthCalledWith(1, 'render:setupMiddleware', server.app)
expect(server.nuxt.callHook).nthCalledWith(2, 'render:errorMiddleware', server.app) expect(server.nuxt.callHook).nthCalledWith(2, 'render:errorMiddleware', server.app)
expect(server.useMiddleware).toBeCalledTimes(5) expect(server.useMiddleware).toBeCalledTimes(4)
expect(serveStatic).toBeCalledTimes(2) expect(serveStatic).toBeCalledTimes(2)
expect(serveStatic).nthCalledWith(1, 'resolve(/var/nuxt/src, var/nuxt/static)', server.options.render.static) expect(serveStatic).nthCalledWith(1, 'resolve(/var/nuxt/src, var/nuxt/static)', server.options.render.static)
expect(server.useMiddleware).nthCalledWith(1, { expect(server.useMiddleware).nthCalledWith(1, {
@ -195,17 +189,6 @@ describe('server: server', () => {
path: '__nuxt_test' path: '__nuxt_test'
}) })
const modernMiddleware = {
serverContext: server.serverContext
}
expect(createModernMiddleware).toBeCalledTimes(1)
expect(createModernMiddleware).toBeCalledWith(modernMiddleware)
expect(server.useMiddleware).nthCalledWith(3, {
id: 'test-modern-middleware',
...modernMiddleware
})
const nuxtMiddlewareOpts = { const nuxtMiddlewareOpts = {
options: server.options, options: server.options,
nuxt: server.nuxt, nuxt: server.nuxt,
@ -214,7 +197,7 @@ describe('server: server', () => {
} }
expect(nuxtMiddleware).toBeCalledTimes(1) expect(nuxtMiddleware).toBeCalledTimes(1)
expect(nuxtMiddleware).toBeCalledWith(nuxtMiddlewareOpts) expect(nuxtMiddleware).toBeCalledWith(nuxtMiddlewareOpts)
expect(server.useMiddleware).nthCalledWith(4, { expect(server.useMiddleware).nthCalledWith(3, {
id: 'test-nuxt-middleware', id: 'test-nuxt-middleware',
...nuxtMiddlewareOpts ...nuxtMiddlewareOpts
}) })
@ -225,7 +208,7 @@ describe('server: server', () => {
} }
expect(errorMiddleware).toBeCalledTimes(1) expect(errorMiddleware).toBeCalledTimes(1)
expect(errorMiddleware).toBeCalledWith(errorMiddlewareOpts) expect(errorMiddleware).toBeCalledWith(errorMiddlewareOpts)
expect(server.useMiddleware).nthCalledWith(5, { expect(server.useMiddleware).nthCalledWith(4, {
id: 'test-error-middleware', id: 'test-error-middleware',
...errorMiddlewareOpts ...errorMiddlewareOpts
}) })
@ -288,7 +271,7 @@ describe('server: server', () => {
expect(launchMiddleware).toBeCalledTimes(1) expect(launchMiddleware).toBeCalledTimes(1)
expect(launchMiddleware).toBeCalledWith({ id: 'test-editor' }) expect(launchMiddleware).toBeCalledWith({ id: 'test-editor' })
expect(server.useMiddleware).nthCalledWith(4, { expect(server.useMiddleware).nthCalledWith(3, {
handler: { id: 'test-editor' }, handler: { id: 'test-editor' },
path: '__open-in-editor' path: '__open-in-editor'
}) })

View File

@ -12,8 +12,10 @@
"fs-extra": "^7.0.1", "fs-extra": "^7.0.1",
"hash-sum": "^1.0.2", "hash-sum": "^1.0.2",
"proper-lockfile": "^4.1.1", "proper-lockfile": "^4.1.1",
"semver": "^6.0.0",
"serialize-javascript": "^1.7.0", "serialize-javascript": "^1.7.0",
"signal-exit": "^3.0.2" "signal-exit": "^3.0.2",
"ua-parser-js": "^0.7.19"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

View File

@ -7,3 +7,4 @@ export * from './serialize'
export * from './task' export * from './task'
export * from './timer' export * from './timer'
export * from './cjs' export * from './cjs'
export * from './modern'

View File

@ -1,7 +1,19 @@
import UAParser from 'ua-parser-js' import UAParser from 'ua-parser-js'
import semver from 'semver' import semver from 'semver'
import ModernBrowsers from './modern-browsers' export const ModernBrowsers = {
Edge: '16',
Firefox: '60',
Chrome: '61',
'Chrome Headless': '61',
Chromium: '61',
Iron: '61',
Safari: '10.1',
Opera: '48',
Yandex: '18',
Vivaldi: '1.14',
'Mobile Safari': '10.3'
}
const modernBrowsers = Object.keys(ModernBrowsers) const modernBrowsers = Object.keys(ModernBrowsers)
.reduce((allBrowsers, browser) => { .reduce((allBrowsers, browser) => {
@ -9,7 +21,7 @@ const modernBrowsers = Object.keys(ModernBrowsers)
return allBrowsers return allBrowsers
}, {}) }, {})
const isModernBrowser = (ua) => { export const isModernBrowser = (ua) => {
if (!ua) { if (!ua) {
return false return false
} }
@ -21,20 +33,16 @@ const isModernBrowser = (ua) => {
return Boolean(modernBrowsers[browser.name] && semver.gte(browserVersion, modernBrowsers[browser.name])) return Boolean(modernBrowsers[browser.name] && semver.gte(browserVersion, modernBrowsers[browser.name]))
} }
const detectModernBrowser = ({ socket = {}, headers }) => { export const isModernRequest = (req, modernMode = false) => {
if (socket.isModernBrowser === undefined) { if (modernMode === false) {
return false
}
const { socket = {}, headers } = req
if (socket._modern === undefined) {
const ua = headers && headers['user-agent'] const ua = headers && headers['user-agent']
socket.isModernBrowser = isModernBrowser(ua) socket._modern = isModernBrowser(ua)
} }
return socket.isModernBrowser return socket._modern
}
export default ({ serverContext }) => {
return (req, res, next) => {
if (serverContext.options.modern !== false) {
req._modern = detectModernBrowser(req)
}
next()
}
} }

View File

@ -8,6 +8,7 @@ import * as serialize from '../src/serialize'
import * as task from '../src/task' import * as task from '../src/task'
import * as timer from '../src/timer' import * as timer from '../src/timer'
import * as cjs from '../src/cjs' import * as cjs from '../src/cjs'
import * as modern from '../src/modern'
describe('util: entry', () => { describe('util: entry', () => {
test('should export all methods from utils folder', () => { test('should export all methods from utils folder', () => {
@ -20,7 +21,8 @@ describe('util: entry', () => {
...serialize, ...serialize,
...task, ...task,
...timer, ...timer,
...cjs ...cjs,
...modern
}) })
}) })
}) })

View File

@ -0,0 +1,60 @@
import { isModernRequest } from '../src/modern'
const createRequest = () => ({
socket: {}, headers: {}
})
describe('utils: modern', () => {
test('should not detect modern build if modern mode is specified', () => {
const req = createRequest()
isModernRequest(req)
isModernRequest(req, 'client')
isModernRequest(req, 'server')
expect(req.socket._modern).toEqual(false)
})
test('should not detect modern browser if connect has been detected', () => {
const req = createRequest()
req.socket = { _modern: true }
isModernRequest(req, 'server')
expect(req.socket._modern).toEqual(true)
})
test('should detect modern browser based on user-agent', () => {
const req = createRequest()
const ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
req.headers['user-agent'] = ua
req.socket = {}
isModernRequest(req, 'server')
expect(req.socket._modern).toEqual(true)
expect(req.socket._modern).toEqual(true)
})
test('should detect legacy browser based on user-agent', () => {
const req = createRequest()
const ua = 'Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US))'
req.headers['user-agent'] = ua
req.socket = {}
isModernRequest(req, 'client')
expect(req.socket._modern).toEqual(false)
})
test('should ignore illegal user-agent', () => {
const req = createRequest()
const ua = 'illegal user agent'
req.headers['user-agent'] = ua
req.socket = {}
isModernRequest(req, 'client')
expect(req.socket._modern).toEqual(false)
})
})

View File

@ -3,6 +3,7 @@ import fs from 'fs-extra'
import chalk from 'chalk' import chalk from 'chalk'
import consola from 'consola' import consola from 'consola'
import template from 'lodash/template' import template from 'lodash/template'
import { isModernRequest } from '@nuxt/utils'
import SPARenderer from './renderers/spa' import SPARenderer from './renderers/spa'
import SSRRenderer from './renderers/ssr' import SSRRenderer from './renderers/ssr'
@ -280,7 +281,8 @@ export default class VueRenderer {
// renderContext.modern // renderContext.modern
if (renderContext.modern === undefined) { if (renderContext.modern === undefined) {
renderContext.modern = req._modern || this.options.modern === 'client' const modernMode = this.options.modern
renderContext.modern = modernMode === 'client' || isModernRequest(req, modernMode)
} }
// Call renderContext hook // Call renderContext hook

View File

@ -3,6 +3,7 @@ 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'
import LRU from 'lru-cache' import LRU from 'lru-cache'
import { isModernRequest } from '@nuxt/utils'
import BaseRenderer from './base' import BaseRenderer from './base'
export default class SPARenderer extends BaseRenderer { export default class SPARenderer extends BaseRenderer {
@ -36,7 +37,8 @@ export default class SPARenderer extends BaseRenderer {
async render(renderContext) { async render(renderContext) {
const { url = '/', req = {}, _generate } = renderContext const { url = '/', req = {}, _generate } = renderContext
const modern = req._modern || (this.options.modern && _generate) const modernMode = this.options.modern
const modern = (modernMode && _generate) || isModernRequest(req, modernMode)
const cacheKey = `${modern ? 'modern:' : 'legacy:'}${url}` const cacheKey = `${modern ? 'modern:' : 'legacy:'}${url}`
let meta = this.cache.get(cacheKey) let meta = this.cache.get(cacheKey)

View File

@ -6,7 +6,7 @@ import webpackDevMiddleware from 'webpack-dev-middleware'
import webpackHotMiddleware from 'webpack-hot-middleware' import webpackHotMiddleware from 'webpack-hot-middleware'
import consola from 'consola' import consola from 'consola'
import { parallel, sequence, wrapArray } from '@nuxt/utils' import { parallel, sequence, wrapArray, isModernRequest } from '@nuxt/utils'
import AsyncMFS from './utils/async-mfs' import AsyncMFS from './utils/async-mfs'
import * as WebpackConfigs from './config' import * as WebpackConfigs from './config'
@ -204,7 +204,7 @@ export class WebpackBundler {
} }
async middleware(req, res, next) { async middleware(req, res, next) {
const name = req._modern ? 'modern' : 'client' const name = isModernRequest(req, this.buildContext.options.modern) ? '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)