UNPKG

bankai

Version:

The easiest way to compile JavaScript, HTML and CSS

225 lines (196 loc) 6.53 kB
var EventEmitter = require('events').EventEmitter var gzipMaybe = require('http-gzip-maybe') var assert = require('assert') var path = require('path') var pump = require('pump') var send = require('send') var Router = require('./lib/regex-router') var bankai = require('./') module.exports = start function start (entry, opts) { opts = opts || {} assert.strictEqual(typeof entry, 'string', 'bankai/http: entry should be type string') assert.strictEqual(typeof opts, 'object', 'bankai/http: opts should be type object') opts = Object.assign({ reload: true }, opts) var compiler = bankai(entry, opts) var router = new Router() var emitter = new EventEmitter() var id = 0 var state = { sse: 0 } compiler.on('ssr', function (result) { state.ssr = result }) compiler.on('change', function (nodeName, edgeName, nodeState) { var node = nodeState[nodeName][edgeName] var name = nodeName + ':' + edgeName if (name === 'documents:index.html') emitter.emit('documents:index.html', node) if (name === 'styles:bundle') emitter.emit('styles:bundle', node) }) router.route(/^\/(favicon\.)(ico|png|gif)$/, function (req, res, params) { var filename = params[1] + params[2] pump(send(req, filename), res) }) router.route(/^\/manifest\.json$/, function (req, res, params) { compiler.manifest(function (err, node) { if (err) { res.statusCode = 404 return res.end(err.message) } res.setHeader('content-type', 'application/json') gzip(node.buffer, req, res) }) }) router.route(/\/(service-worker\.js)|(\/sw\.js)$/, function (req, res, params) { compiler.serviceWorker(function (err, node) { if (err) { res.statusCode = 404 return res.end(err.message) } res.setHeader('content-type', 'application/javascript') gzip(node.buffer, req, res) }) }) router.route(/^\/(assets|content|public)\/([^?]*)(\?.*)?$/, function (req, res, params) { var prefix = params[1] // asset dir var name = prefix + '/' + params[2] compiler.assets(name, function (err, filename) { if (err) { res.statusCode = 404 return res.end(err.message) } pump(send(req, filename), res) }) }) router.route(/\/([a-zA-Z0-9-_.]+)\.js(\?.*)?$/, function (req, res, params) { var name = params[1] compiler.scripts(name, function (err, node) { if (err) { res.statusCode = 404 return res.end(err.message) } res.setHeader('content-type', 'application/javascript') gzip(node.buffer, req, res) }) }) router.route(/\/([a-zA-Z0-9-_.]+)\.css(\?.*)?$/, function (req, res, params) { var name = params[1] compiler.styles(name, function (err, node) { if (err) { res.statusCode = 404 return res.end(err.message) } res.setHeader('content-type', 'text/css') gzip(node.buffer, req, res) }) }) // Source maps. Each source map is stored as 'foo.js.map' within their // respective node. So in order to figure out the right source map we must // derive figure out where the extension comes from. router.route(/\/([a-zA-Z0-9-_.]+)\.map$/, function (req, res, params) { var source = params[1] var ext = path.extname(source.replace(/\.map$/, '')) var type = source === 'bankai-reload.js' ? 'reload' : source === 'bankai-service-worker.js' ? 'service-worker' : ext === '.js' ? 'scripts' : 'unknown' compiler.sourceMaps(type, source, function (err, node) { if (err) { res.statusCode = 404 return res.end(err.message) } res.setHeader('content-type', 'application/json') gzip(node.buffer, req, res) }) }) router.route(/\/reload/, function sse (req, res) { var connected = true emitter.on('documents:index.html', reloadScript) emitter.on('styles:bundle', reloadStyle) state.sse += 1 compiler.emit('sse-connect') res.writeHead(200, { 'Content-Type': 'text/event-stream', 'X-Accel-Buffering': 'no', 'Cache-Control': 'no-cache' }) res.write('retry: 10000\n') var interval = setInterval(function () { if (res.finished) return // prevent writes after stream has closed res.write(`id:${id++}\ndata:{ "type:": "heartbeat" }\n\n`) }, 4000) req.on('error', disconnect) res.on('error', disconnect) res.on('close', disconnect) res.on('finish', disconnect) function disconnect () { clearInterval(interval) if (connected) { emitter.removeListener('documents:index.html', reloadScript) emitter.removeListener('styles:bundle', reloadStyle) connected = false state.sse -= 1 compiler.emit('sse-disconnect') } } function reloadScript (node) { var msg = JSON.stringify({ type: 'scripts' }) res.write(`id:${id++}\ndata:${msg}\n\n`) } function reloadStyle (node) { var msg = JSON.stringify({ type: 'styles', bundle: node.buffer.toString() }) res.write(`id:${id++}\ndata:${msg}\n\n`) } }) router.default(function (req, res, next) { var url = req.url if (state.ssr && state.ssr.renderRoute) { state.ssr.renderRoute(url, function (err, buffer) { if (err) { state.ssr.success = false state.ssr.error = err return sendDocument(url, req, res, next) } res.setHeader('content-type', 'text/html') gzip(buffer, req, res) }) } else { return sendDocument(url, req, res, next) } }) function sendDocument (url, req, res, next) { compiler.documents(url, function (err, node) { if (err) { return compiler.documents('/404', function (err, node) { if (err) return next() // No matches found, call next res.statusCode = 404 res.setHeader('content-type', 'text/html') gzip(node.buffer, req, res) }) } res.setHeader('content-type', 'text/html') gzip(node.buffer, req, res) }) } // TODO: move all UI code out of this file handler.state = state // Expose compiler so we can use it in `bankai start` handler.compiler = compiler return handler // Return a handler to listen. function handler (req, res, next) { router.match(req, res, next) } } function gzip (buffer, req, res) { var zipper = gzipMaybe(req, res) pump(zipper, res) zipper.end(buffer) }