mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Drop prepare requirement from production server (#5351)
As prepare is only needed to boot up the hot reloader + exportPathMap routes in development, it's not longer a requirement in the production server.
This commit is contained in:
parent
0b6ecf1fe4
commit
3d94ae0a7d
|
@ -1,6 +1,7 @@
|
||||||
import Server from './next-server'
|
import Server from './next-server'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import HotReloader from './hot-reloader'
|
import HotReloader from './hot-reloader'
|
||||||
|
import {route} from './router'
|
||||||
import {PHASE_DEVELOPMENT_SERVER} from '../lib/constants'
|
import {PHASE_DEVELOPMENT_SERVER} from '../lib/constants'
|
||||||
|
|
||||||
export default class DevServer extends Server {
|
export default class DevServer extends Server {
|
||||||
|
@ -19,8 +20,37 @@ export default class DevServer extends Server {
|
||||||
return 'development'
|
return 'development'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addExportPathMapRoutes () {
|
||||||
|
// Makes `next export` exportPathMap work in development mode.
|
||||||
|
// So that the user doesn't have to define a custom server reading the exportPathMap
|
||||||
|
if (this.nextConfig.exportPathMap) {
|
||||||
|
console.log('Defining routes from exportPathMap')
|
||||||
|
const exportPathMap = await this.nextConfig.exportPathMap({}, {dev: true, dir: this.dir, outDir: null, distDir: this.distDir, buildId: this.buildId}) // In development we can't give a default path mapping
|
||||||
|
for (const path in exportPathMap) {
|
||||||
|
const {page, query = {}} = exportPathMap[path]
|
||||||
|
|
||||||
|
// We use unshift so that we're sure the routes is defined before Next's default routes
|
||||||
|
this.router.add({
|
||||||
|
match: route(path),
|
||||||
|
fn: async (req, res, params, parsedUrl) => {
|
||||||
|
const { query: urlQuery } = parsedUrl
|
||||||
|
|
||||||
|
Object.keys(urlQuery)
|
||||||
|
.filter(key => query[key] === undefined)
|
||||||
|
.forEach(key => console.warn(`Url defines a query parameter '${key}' that is missing in exportPathMap`))
|
||||||
|
|
||||||
|
const mergedQuery = {...urlQuery, ...query}
|
||||||
|
|
||||||
|
await this.render(req, res, page, mergedQuery, parsedUrl)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async prepare () {
|
async prepare () {
|
||||||
await super.prepare()
|
await super.prepare()
|
||||||
|
await this.addExportPathMapRoutes()
|
||||||
await this.hotReloader.start()
|
await this.hotReloader.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,45 +67,19 @@ export default class DevServer extends Server {
|
||||||
return super.run(req, res, parsedUrl)
|
return super.run(req, res, parsedUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateRoutes () {
|
generateRoutes () {
|
||||||
const routes = await super.generateRoutes()
|
const routes = super.generateRoutes()
|
||||||
|
|
||||||
// In development we expose all compiled files for react-error-overlay's line show feature
|
// In development we expose all compiled files for react-error-overlay's line show feature
|
||||||
// We use unshift so that we're sure the routes is defined before Next's default routes
|
// We use unshift so that we're sure the routes is defined before Next's default routes
|
||||||
routes.unshift({
|
routes.unshift({
|
||||||
path: '/_next/development/:path*',
|
match: route('/_next/development/:path*'),
|
||||||
fn: async (req, res, params) => {
|
fn: async (req, res, params) => {
|
||||||
const p = join(this.distDir, ...(params.path || []))
|
const p = join(this.distDir, ...(params.path || []))
|
||||||
await this.serveStatic(req, res, p)
|
await this.serveStatic(req, res, p)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Makes `next export` exportPathMap work in development mode.
|
|
||||||
// So that the user doesn't have to define a custom server reading the exportPathMap
|
|
||||||
if (this.nextConfig.exportPathMap) {
|
|
||||||
console.log('Defining routes from exportPathMap')
|
|
||||||
const exportPathMap = await this.nextConfig.exportPathMap({}, {dev: true, dir: this.dir, outDir: null, distDir: this.distDir, buildId: this.buildId}) // In development we can't give a default path mapping
|
|
||||||
for (const path in exportPathMap) {
|
|
||||||
const {page, query = {}} = exportPathMap[path]
|
|
||||||
|
|
||||||
// We use unshift so that we're sure the routes is defined before Next's default routes
|
|
||||||
routes.unshift({
|
|
||||||
path,
|
|
||||||
fn: async (req, res, params, parsedUrl) => {
|
|
||||||
const { query: urlQuery } = parsedUrl
|
|
||||||
|
|
||||||
Object.keys(urlQuery)
|
|
||||||
.filter(key => query[key] === undefined)
|
|
||||||
.forEach(key => console.warn(`Url defines a query parameter '${key}' that is missing in exportPathMap`))
|
|
||||||
|
|
||||||
const mergedQuery = {...urlQuery, ...query}
|
|
||||||
|
|
||||||
await this.render(req, res, page, mergedQuery, parsedUrl)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return routes
|
return routes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
sendHTML,
|
sendHTML,
|
||||||
serveStatic
|
serveStatic
|
||||||
} from './render'
|
} from './render'
|
||||||
import Router from './router'
|
import Router, {route} from './router'
|
||||||
import { isInternalUrl } from './utils'
|
import { isInternalUrl } from './utils'
|
||||||
import loadConfig from './config'
|
import loadConfig from './config'
|
||||||
import {PHASE_PRODUCTION_SERVER, BLOCKED_PAGES, BUILD_ID_FILE, CLIENT_STATIC_FILES_PATH, CLIENT_STATIC_FILES_RUNTIME} from '../lib/constants'
|
import {PHASE_PRODUCTION_SERVER, BLOCKED_PAGES, BUILD_ID_FILE, CLIENT_STATIC_FILES_PATH, CLIENT_STATIC_FILES_RUNTIME} from '../lib/constants'
|
||||||
|
@ -21,7 +21,6 @@ export default class Server {
|
||||||
constructor ({ dir = '.', staticMarkup = false, quiet = false, conf = null } = {}) {
|
constructor ({ dir = '.', staticMarkup = false, quiet = false, conf = null } = {}) {
|
||||||
this.dir = resolve(dir)
|
this.dir = resolve(dir)
|
||||||
this.quiet = quiet
|
this.quiet = quiet
|
||||||
this.router = new Router()
|
|
||||||
const phase = this.currentPhase()
|
const phase = this.currentPhase()
|
||||||
this.nextConfig = loadConfig(phase, this.dir, conf)
|
this.nextConfig = loadConfig(phase, this.dir, conf)
|
||||||
this.distDir = join(this.dir, this.nextConfig.distDir)
|
this.distDir = join(this.dir, this.nextConfig.distDir)
|
||||||
|
@ -50,6 +49,8 @@ export default class Server {
|
||||||
publicRuntimeConfig
|
publicRuntimeConfig
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const routes = this.generateRoutes()
|
||||||
|
this.router = new Router(routes)
|
||||||
this.setAssetPrefix(assetPrefix)
|
this.setAssetPrefix(assetPrefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,9 +87,8 @@ export default class Server {
|
||||||
asset.setAssetPrefix(this.renderOpts.assetPrefix)
|
asset.setAssetPrefix(this.renderOpts.assetPrefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
async prepare () {
|
// Backwards compatibility
|
||||||
await this.defineRoutes()
|
async prepare () {}
|
||||||
}
|
|
||||||
|
|
||||||
// Backwards compatibility
|
// Backwards compatibility
|
||||||
async close () {}
|
async close () {}
|
||||||
|
@ -97,10 +97,10 @@ export default class Server {
|
||||||
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
|
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateRoutes () {
|
generateRoutes () {
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/_next/static/:path*',
|
match: route('/_next/static/:path*'),
|
||||||
fn: async (req, res, params) => {
|
fn: async (req, res, params) => {
|
||||||
// The commons folder holds commonschunk files
|
// The commons folder holds commonschunk files
|
||||||
// The chunks folder holds dynamic entries
|
// The chunks folder holds dynamic entries
|
||||||
|
@ -113,7 +113,7 @@ export default class Server {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/_next/:path*',
|
match: route('/_next/:path*'),
|
||||||
// This path is needed because `render()` does a check for `/_next` and the calls the routing again
|
// This path is needed because `render()` does a check for `/_next` and the calls the routing again
|
||||||
fn: async (req, res, params, parsedUrl) => {
|
fn: async (req, res, params, parsedUrl) => {
|
||||||
await this.render404(req, res, parsedUrl)
|
await this.render404(req, res, parsedUrl)
|
||||||
|
@ -124,7 +124,7 @@ export default class Server {
|
||||||
// (but it should support as many as params, seperated by '/')
|
// (but it should support as many as params, seperated by '/')
|
||||||
// Othewise this will lead to a pretty simple DOS attack.
|
// Othewise this will lead to a pretty simple DOS attack.
|
||||||
// See more: https://github.com/zeit/next.js/issues/2617
|
// See more: https://github.com/zeit/next.js/issues/2617
|
||||||
path: '/static/:path*',
|
match: route('/static/:path*'),
|
||||||
fn: async (req, res, params) => {
|
fn: async (req, res, params) => {
|
||||||
const p = join(this.dir, 'static', ...(params.path || []))
|
const p = join(this.dir, 'static', ...(params.path || []))
|
||||||
await this.serveStatic(req, res, p)
|
await this.serveStatic(req, res, p)
|
||||||
|
@ -138,7 +138,7 @@ export default class Server {
|
||||||
// Othewise this will lead to a pretty simple DOS attack.
|
// Othewise this will lead to a pretty simple DOS attack.
|
||||||
// See more: https://github.com/zeit/next.js/issues/2617
|
// See more: https://github.com/zeit/next.js/issues/2617
|
||||||
routes.push({
|
routes.push({
|
||||||
path: '/:path*',
|
match: route('/:path*'),
|
||||||
fn: async (req, res, params, parsedUrl) => {
|
fn: async (req, res, params, parsedUrl) => {
|
||||||
const { pathname, query } = parsedUrl
|
const { pathname, query } = parsedUrl
|
||||||
await this.render(req, res, pathname, query, parsedUrl)
|
await this.render(req, res, pathname, query, parsedUrl)
|
||||||
|
@ -149,16 +149,6 @@ export default class Server {
|
||||||
return routes
|
return routes
|
||||||
}
|
}
|
||||||
|
|
||||||
async defineRoutes () {
|
|
||||||
const routes = await this.generateRoutes()
|
|
||||||
|
|
||||||
for (const method of ['GET', 'HEAD']) {
|
|
||||||
for (const route of routes) {
|
|
||||||
this.router.add(method, route.path, route.fn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async run (req, res, parsedUrl) {
|
async run (req, res, parsedUrl) {
|
||||||
const fn = this.router.match(req, res, parsedUrl)
|
const fn = this.router.match(req, res, parsedUrl)
|
||||||
if (fn) {
|
if (fn) {
|
||||||
|
|
|
@ -1,29 +1,26 @@
|
||||||
import pathMatch from './lib/path-match'
|
import pathMatch from './lib/path-match'
|
||||||
|
|
||||||
const route = pathMatch()
|
export const route = pathMatch()
|
||||||
|
|
||||||
export default class Router {
|
export default class Router {
|
||||||
constructor () {
|
constructor (routes = []) {
|
||||||
this.routes = new Map()
|
this.routes = routes
|
||||||
}
|
}
|
||||||
|
|
||||||
add (method, path, fn) {
|
add (route) {
|
||||||
const routes = this.routes.get(method) || new Set()
|
this.routes.unshift(route)
|
||||||
routes.add({ match: route(path), fn })
|
|
||||||
this.routes.set(method, routes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match (req, res, parsedUrl) {
|
match (req, res, parsedUrl) {
|
||||||
const routes = this.routes.get(req.method)
|
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
||||||
if (!routes) return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const { pathname } = parsedUrl
|
const { pathname } = parsedUrl
|
||||||
for (const r of routes) {
|
for (const route of this.routes) {
|
||||||
const params = r.match(pathname)
|
const params = route.match(pathname)
|
||||||
if (params) {
|
if (params) {
|
||||||
return async () => {
|
return () => route.fn(req, res, params, parsedUrl)
|
||||||
return r.fn(req, res, params, parsedUrl)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,21 @@ describe('Production Usage', () => {
|
||||||
expect(res.status).toBe(404)
|
expect(res.status).toBe(404)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should render 501 if the HTTP method is not GET or HEAD', async () => {
|
||||||
|
const url = `http://localhost:${appPort}/_next/abcdef`
|
||||||
|
const methods = ['POST', 'PUT', 'DELETE']
|
||||||
|
for (const method of methods) {
|
||||||
|
const res = await fetch(url, {method})
|
||||||
|
expect(res.status).toBe(501)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should set Content-Length header', async () => {
|
||||||
|
const url = `http://localhost:${appPort}`
|
||||||
|
const res = await fetch(url)
|
||||||
|
expect(res.headers.get('Content-Length')).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
it('should set Cache-Control header', async () => {
|
it('should set Cache-Control header', async () => {
|
||||||
const buildId = readFileSync(join(__dirname, '../.next/BUILD_ID'), 'utf8')
|
const buildId = readFileSync(join(__dirname, '../.next/BUILD_ID'), 'utf8')
|
||||||
const buildManifest = require(join('../.next', BUILD_MANIFEST))
|
const buildManifest = require(join('../.next', BUILD_MANIFEST))
|
||||||
|
|
Loading…
Reference in a new issue