"use strict" ;
const budo = require ( "budo" ) ;
const defaultValue = require ( "default-value" ) ;
const assureArray = require ( "assure-array" ) ;
const path = require ( "path" ) ;
const entities = require ( "entities" ) ;
const util = require ( "util" ) ;
let reloadClientTag = Buffer . from ( "<script src=\"/budo/livereload.js\"></script>" ) ;
function monkeyPatchEnd ( res ) {
// NOTE: This is a hack, to auto-inject the Budo livereload client in any outgoing HTML response (but only in development mode).
let end = res . end . bind ( res ) ;
res . end = function monkeyPatchedEnd ( ... args ) {
let originalChunk = args [ 0 ] ;
let prefix ;
if ( originalChunk == null || typeof originalChunk === "string" || Buffer . isBuffer ( originalChunk ) ) {
if ( ! res . headersSent ) {
// If headers have already been sent, we'll just have to hope that the browser will still look at our appended tag, as we can't change the response size anymore...
// TODO: Make this more robust in the future
let typeHeader = res . getHeader ( "content-type" ) ;
if ( typeHeader != null && typeHeader . startsWith ( "text/html" ) ) {
let contentLength = res . getHeader ( "content-length" ) ;
if ( contentLength != null ) {
// Compensate for the additional bytes introduced by our injected script tag
res . setHeader ( "content-length" , parseInt ( contentLength ) + reloadClientTag . length ) ;
}
prefix = reloadClientTag ;
}
}
// Reset the `end` method back to the original method; we don't need to get in the way anymore
res . end = end ;
let originalChunkAsBuffer = ( typeof originalChunk === "string" )
? Buffer . from ( originalChunk )
: originalChunk ;
if ( prefix != null ) {
let chunk = ( originalChunkAsBuffer == null )
? reloadClientTag
: Buffer . concat ( [ originalChunkAsBuffer , reloadClientTag ] ) ;
end ( chunk , ... args . slice ( 1 ) ) ;
} else {
end ( ... args ) ;
}
} else {
throw new Error ( ` Expected a buffer or string in 'end', but got something else: ${ util . inspect ( args [ 0 ] ) } ` ) ;
}
} ;
}
function createExpressMiddleware ( app ) {
return function expressMiddleware ( req , res , next ) {
try {
monkeyPatchEnd ( res ) ;
} catch ( error ) {
process . nextTick ( ( ) => {
throw error ;
} ) ;
}
app . handle ( req , res , ( err ) => {
if ( err != null && err instanceof Error ) {
res
. status ( 500 )
. send ( ` ${ reloadClientTag } <pre> ${ entities . escape ( err . stack ) } </pre> ` ) ;
} else {
next ( err ) ;
}
} ) ;
} ;
}
module . exports = function ( { options , staticPath , staticBasePath , entryPaths , host } ) {
let middlewareList ;
if ( options . middleware != null ) {
middlewareList = assureArray ( options . middleware ) . concat ( [
createExpressMiddleware ( options . expressApp )
] ) ;
} else {
middlewareList = [
createExpressMiddleware ( options . expressApp )
] ;
}
let budoOptions = {
watchGlob : staticPath ( options . livereloadPattern ) ,
live : options . livereloadPattern ,
livePort : defaultValue ( options . livereloadPort , 35729 ) ,
stream : defaultValue ( options . stream , process . stdout ) ,
port : options . port ,
host : host ,
dir : staticBasePath ,
serve : ( options . staticPrefix != null )
? path . join ( options . staticPrefix , options . bundlePath )
: options . bundlePath ,
browserify : options . browserify ,
middleware : middlewareList ,
debug : defaultValue ( options . sourceMaps , true ) ,
} ;
let devServer = budo ( entryPaths , budoOptions ) . on ( "connect" , ( event ) => {
let reloadServer = event . webSocketServer ;
reloadServer . once ( "connection" , ( _socket ) => {
// This is to make sure the browser also reloads after the process has auto-restarted (eg. using `nodemon`)
console . log ( "Triggering initial page refresh for new client..." ) ;
devServer . reload ( "*" ) ;
} ) ;
} ) ;
} ;