"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(""); 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}
${entities.escape(err.stack)}
`); } 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("*"); }); }); };