asdf-games/node_modules/budo/lib/middleware.js

255 lines
8.1 KiB
JavaScript

// TODO: Expose this like webpack-dev-server middleware
var stacked = require('stacked')
var serveStatic = require('serve-static')
var defaultIndex = require('simple-html-index')
var logger = require('./simple-http-logger')
var urlLib = require('url')
var xtend = require('xtend')
var pushState = require('connect-pushstate')
var liveReload = require('inject-lr-script')
var urlTrim = require('url-trim')
var escapeHtml = require('escape-html')
var fs = require('fs')
var browserify = require('browserify')
var path = require('path')
var liveReloadClientFile = path.resolve(__dirname, 'reload', 'client.js')
var bundledReloadClientFile = path.resolve(__dirname, '..', 'build', 'bundled-livereload-client.js')
// Patch 'wasm' since send has not yet been updated to latest 'mime' module
serveStatic.mime.types['wasm'] = 'application/wasm'
module.exports = budoMiddleware
function budoMiddleware (entryMiddleware, opts) {
opts = opts || {}
var staticPaths = [].concat(opts.dir).filter(Boolean)
if (staticPaths.length === 0) {
staticPaths = [ process.cwd() ]
}
var entrySrc = opts.serve
var live = opts.live
var cors = opts.cors
var handler = stacked()
var middlewares = [].concat(opts.middleware).filter(Boolean)
// Everything is logged except favicon.ico
var ignoreLog = [].concat(opts.ignoreLog).filter(Boolean)
var logHandler = logger({
ignores: [ '/favicon.ico' ].concat(ignoreLog)
})
handler.use(function (req, res, next) {
if (cors) {
res.setHeader('Access-Control-Allow-Headers', 'Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With')
res.setHeader('Access-Control-Allow-Methods', 'GET, PUT, POST')
res.setHeader('Access-Control-Allow-Origin', '*')
}
logHandler(req, res, next)
})
// User middleware(s) can override others
middlewares.forEach(function (middleware) {
if (typeof middleware !== 'function') {
throw new Error('middleware options must be functions')
}
handler.use(function (req, res, next) {
logHandler.type = 'middleware'
middleware(req, res, next)
})
})
// Entry (watchify) middleware
if (entryMiddleware) {
var entryRoute = urlLib.parse(entrySrc).pathname
if (!/^\//.test(entryRoute)) entryRoute = '/' + entryRoute
handler.use(function (req, res, next) {
if (urlTrim(req.url) === urlTrim(entryRoute)) {
entryMiddleware(req, res, next)
} else {
next()
}
})
}
// Re-route for pushState support
if (opts.pushstate) {
if (typeof opts.pushstate === 'string') {
throw new Error('--pushstate is a string, you shouold use subarg options instead')
}
var pushStateOpts = xtend(typeof opts.pushstate === 'boolean' ? {} : opts.pushstate)
delete pushStateOpts._ // added by subarg
handler.use(pushState(pushStateOpts))
}
// Inject liveReload snippet on response
var liveInjector = liveReload({
local: true
})
// this is lazily set and cannot be changed dynamically
var liveScriptUrl
// By default, attempt to optimize the response
var shouldUseBundledLiveReload = true
// Cache the client by default to optimize the response
var liveReloadClient
handler.use(liveReloadHandler)
// Ignore favicon clutter
handler.mount('/favicon.ico', favicon)
// If the user wishes to *always* serve
// a generated index instead of a static one.
if (opts.forceDefaultIndex) {
handler.use(indexHandler)
}
// Static assets (html/images/etc)
staticPaths.forEach(function (rootFile) {
var staticOpts = xtend({
cacheControl: false
}, opts.staticOptions)
delete staticOpts._ // from subarg
var staticHandler = serveStatic(rootFile, staticOpts)
handler.use(function (req, res, next) {
logHandler.type = 'static'
staticHandler(req, res, next)
})
})
// Generates a default index.html
// when none is found locally.
handler.use(indexHandler)
// Handle errors
handler.use(function (req, res) {
res.statusCode = 404
res.end('404 not found: ' + escapeHtml(req.url))
})
// Allow live options to be changed at runtime
handler.setLiveOptions = setLiveOptions
return handler
function setLiveOptions (opts) {
live = opts
}
function favicon (req, res) {
var maxAge = 345600 // 4 days
res.setHeader('Cache-Control', 'public, max-age=' + Math.floor(maxAge / 1000))
res.setHeader('Content-Type', 'image/x-icon')
res.statusCode = 200
res.end()
}
function indexHandler (req, res, next) {
if (urlLib.parse(req.url).pathname === '/' || /\/index.html?/i.test(req.url)) {
// If we reach this, our response will be generated
// (not static from local file system)
logHandler.type = 'generated'
res.setHeader('content-type', 'text/html')
var stream = opts.defaultIndex || defaultIndex
stream({
entry: entrySrc,
title: opts.title,
css: opts.css,
base: opts.base === true ? '/' : (opts.base || null)
}, req).pipe(res)
} else {
next()
}
}
function serveBundledLiveReload (res, successCallback) {
if (liveReloadClient) {
res.end(liveReloadClient)
successCallback(true)
} else {
fs.readFile(bundledReloadClientFile, function (err, src) {
if (err) {
if (shouldUseBundledLiveReload) {
console.error([
'NOTE: The bundled LiveReload client could not be found, so budo will',
'generate this on the fly.',
'This is most likely because you are using a git cloned version of budo',
'instead of installing it from npm.',
'You can run this from your cloned budo folder to create a build:',
' npm run bundle-live-client'
].join('\n'))
}
shouldUseBundledLiveReload = false
successCallback(false)
} else {
liveReloadClient = src
res.end(src)
successCallback(true)
}
})
}
}
function serveBrowserifyLiveReload (cache, debug, liveScripts, res) {
// Browserify the client file, e.g. if user has a script to include
if (cache && liveReloadClient) {
res.end(liveReloadClient)
} else {
var b = browserify({ debug: debug })
b.add(liveReloadClientFile)
if (live.expose) {
b.require(liveReloadClientFile, { expose: 'budo-livereload' })
}
liveScripts.forEach(function (file) {
b.add(path.resolve(file))
})
b.bundle(function (err, src) {
if (err) {
console.error('Error bundling LiveReload client:\n' + err.message)
res.statusCode = 500
res.end('Error bundling LiveReload client: ' + err)
} else {
liveReloadClient = src
res.end(src)
}
})
}
}
function liveReloadHandler (req, res, next) {
if (!live || live.plugin) return next()
if (!liveScriptUrl) {
liveScriptUrl = live.path || '/budo/livereload.js'
logHandler.ignores.push(liveScriptUrl)
} else if (liveScriptUrl && live.path && liveScriptUrl !== live.path) {
var errMessage = 'Error: The LiveReload path field cannot be changed dynamically.\n' +
'Please open an issue in budo if you have a specific use case for this.'
console.error(errMessage)
res.statusCode = 500
res.end(errMessage)
return
}
if (req.url === liveScriptUrl) {
res.statusCode = 200
res.setHeader('Content-Type', 'application/javascript')
var liveScripts = (Array.isArray(live.include) ? live.include : [ live.include ]).filter(Boolean)
var cache = live.cache !== false
var debug = live.debug
// Default setup - use a bundled JS file for LiveReload client
if (shouldUseBundledLiveReload && cache && !debug && liveScripts.length === 0) {
serveBundledLiveReload(res, function (success) {
// fall back to browserify on the fly
if (!success) serveBrowserifyLiveReload(cache, debug, liveScripts, res)
})
} else {
serveBrowserifyLiveReload(cache, debug, liveScripts, res)
}
} else {
liveInjector.path = liveScriptUrl
liveInjector(req, res, next)
}
}
}