2018-10-02 21:11:01 +00:00
import Server from 'next-server/dist/server/next-server'
2018-09-28 12:05:23 +00:00
import { join } from 'path'
import HotReloader from './hot-reloader'
2018-10-01 22:55:31 +00:00
import { route } from 'next-server/dist/server/router'
import { PHASE _DEVELOPMENT _SERVER } from 'next-server/constants'
2018-09-28 12:05:23 +00:00
export default class DevServer extends Server {
constructor ( options ) {
super ( options )
this . hotReloader = new HotReloader ( this . dir , { config : this . nextConfig , buildId : this . buildId } )
this . renderOpts . dev = true
}
currentPhase ( ) {
return PHASE _DEVELOPMENT _SERVER
}
readBuildId ( ) {
return 'development'
}
2018-10-01 14:31:47 +00:00
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 )
}
} )
}
}
}
2018-09-28 12:05:23 +00:00
async prepare ( ) {
await super . prepare ( )
2018-10-01 14:31:47 +00:00
await this . addExportPathMapRoutes ( )
2018-09-28 12:32:26 +00:00
await this . hotReloader . start ( )
2018-09-28 12:05:23 +00:00
}
async close ( ) {
2018-09-28 12:32:26 +00:00
await this . hotReloader . stop ( )
2018-09-28 12:05:23 +00:00
}
async run ( req , res , parsedUrl ) {
2018-09-28 12:32:26 +00:00
const { finished } = await this . hotReloader . run ( req , res , parsedUrl )
if ( finished ) {
return
2018-09-28 12:05:23 +00:00
}
return super . run ( req , res , parsedUrl )
}
2018-10-01 14:31:47 +00:00
generateRoutes ( ) {
const routes = super . generateRoutes ( )
2018-09-28 12:05:23 +00:00
// 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
routes . unshift ( {
2018-10-01 14:31:47 +00:00
match : route ( '/_next/development/:path*' ) ,
2018-09-28 12:05:23 +00:00
fn : async ( req , res , params ) => {
const p = join ( this . distDir , ... ( params . path || [ ] ) )
await this . serveStatic ( req , res , p )
}
} )
return routes
}
async renderToHTML ( req , res , pathname , query ) {
const compilationErr = await this . getCompilationError ( pathname )
if ( compilationErr ) {
res . statusCode = 500
return this . renderErrorToHTML ( compilationErr , req , res , pathname , query )
}
2018-11-03 00:24:43 +00:00
// In dev mode we use on demand entries to compile the page before rendering
try {
await this . hotReloader . ensurePage ( pathname )
} catch ( err ) {
if ( err . code === 'ENOENT' ) {
res . statusCode = 404
return this . renderErrorToHTML ( null , req , res , pathname , query )
}
if ( ! this . quiet ) console . error ( err )
}
2018-09-28 12:05:23 +00:00
return super . renderToHTML ( req , res , pathname , query )
}
async renderErrorToHTML ( err , req , res , pathname , query ) {
const compilationErr = await this . getCompilationError ( pathname )
if ( compilationErr ) {
res . statusCode = 500
return super . renderErrorToHTML ( compilationErr , req , res , pathname , query )
}
try {
const out = await super . renderErrorToHTML ( err , req , res , pathname , query )
return out
} catch ( err2 ) {
if ( ! this . quiet ) console . error ( err2 )
res . statusCode = 500
return super . renderErrorToHTML ( err2 , req , res , pathname , query )
}
}
2018-12-09 21:46:45 +00:00
sendHTML ( req , res , html ) {
// In dev, we should not cache pages for any reason.
res . setHeader ( 'Cache-Control' , 'no-store, must-revalidate' )
return super . sendHTML ( req , res , html )
}
2018-09-28 12:05:23 +00:00
setImmutableAssetCacheControl ( res ) {
res . setHeader ( 'Cache-Control' , 'no-store, must-revalidate' )
}
async getCompilationError ( page ) {
const errors = await this . hotReloader . getCompilationErrors ( page )
if ( errors . length === 0 ) return
// Return the very first error we found.
return errors [ 0 ]
}
}