Skip to content

Instantly share code, notes, and snippets.

@vladkosinov
Created August 15, 2016 12:36
Show Gist options
  • Save vladkosinov/6edd951abfb7c55d5bbc4ed222adc536 to your computer and use it in GitHub Desktop.
Save vladkosinov/6edd951abfb7c55d5bbc4ed222adc536 to your computer and use it in GitHub Desktop.
webpack-nodemon-middleware.js
const nodemon = require('nodemon');
const chalk = require('chalk');
const defaultNodemonOptions = {
execMap: { js: 'node' },
runOnChangeOnly: true,
ext: 'noop',
watch: ['noop/'],
ignore: ['*'],
stdout: false,
stderr: false
};
const logPrefix = chalk.green('[webpack-nodemon-middleware]');
function log(...args) {
console.log(...args);
}
module.exports = function createWebpackNodemonMiddleware(compiler, options) {
const nodemonConfig = Object.assign({}, defaultNodemonOptions, options.nodemon);
const readyMessage = options.readyMessage;
const beforeRestart = options.beforeRestart || ((f) => f());
const restartTimeout = options.restartTimeout || 200;
const statsConfig = options.stats || {};
const demon = nodemon(nodemonConfig);
let bundleIsValid = false;
let processIsOnline = false;
let listeners = [];
let restartTimeoutId = null;
function restart() {
beforeRestart(() => {
if (restartTimeoutId !== null) {
clearTimeout(restartTimeoutId);
}
restartTimeoutId = setTimeout(() => {
restartTimeoutId = null;
demon.restart();
}, restartTimeout);
});
}
function notifyListeners() {
const cbs = listeners;
listeners = [];
if (cbs.length) {
log(logPrefix, `continue delayed requests (${cbs.length})`);
}
cbs.forEach(cb => cb());
}
demon
.on('restart', () => {
processIsOnline = false;
log(logPrefix, 'process is offline');
})
.on('stdout', (stdout) => {
log(logPrefix, 'stdout:');
log(stdout.toString().trim());
const includes = stdout.toString().includes(readyMessage);
if (!includes) { return; }
if (!bundleIsValid) { return; }
processIsOnline = true;
log(logPrefix, 'process is online');
notifyListeners();
})
.on('stderr', (stderr) => {
log(logPrefix, 'stderr:', stderr.toString());
});
compiler.plugin('done', (stats) => {
bundleIsValid = true;
log(logPrefix, 'bundle:');
log(stats.toString(statsConfig));
restart();
});
function invalidPlugin() {
if (bundleIsValid) {
log(logPrefix, 'bundle in now INVALID');
}
bundleIsValid = false;
}
function invalidAsyncPlugin(c, cb) {
invalidPlugin();
cb();
}
compiler.plugin('invalid', invalidPlugin);
compiler.plugin('watch-run', invalidAsyncPlugin);
compiler.plugin('run', invalidAsyncPlugin);
compiler.watch({}, (err) => {
if (err) {
throw err;
}
});
function callOnServerReady(cb) {
if (bundleIsValid && processIsOnline) {
return cb();
}
log(logPrefix, 'request delayed until process restarted');
listeners.push(cb);
return null;
}
function webpackNodemonMiddleware(req, res, next) {
callOnServerReady(err => next(err));
}
webpackNodemonMiddleware.restart = restart;
return webpackNodemonMiddleware;
};
@vladkosinov
Copy link
Author

Motivation:

With server side render technique we face tooling difficulties:

  • restart server process on bundle/out-of-bundle files changes
  • pass arguments to server process, e.g port
  • proxy requests to server process
  • delay requests until bundle finished

webpack-nodemon-middleware is trying to solve this problems.

Usage example:

const path = require('path');
const express = require('express');
const proxy = require('http-proxy-middleware');

const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackNodemonMiddleware = require('./util/webpack-nodemon-middleware');

const webpackClientConfig = require('./webpack/config-development.js');
const webpackServerConfig = require('./webpack/config-server.js');

const DEV_SERVER_PORT = 8080;

const PRERENDER_SCRIPT_PATH = path.resolve(__dirname, '../release/server/index.js');
const PRERENDER_HOST = 'localhost';
const PRERENDER_PORT = '8081';

const clientCompiler = webpack(webpackClientConfig);
const serverCompiler = webpack(webpackServerConfig);

const proxyMiddleware = proxy(`http://${PRERENDER_HOST}:${PRERENDER_PORT}`);

const devMiddleware = webpackDevMiddleware(clientCompiler);

const nodemonMiddleware = webpackNodemonMiddleware(serverCompiler, {
    // we are delaying requests until server process in not ready to receive them
    // to determine that server is ready – parse its stdout and check next pattern:
    readyMessage: 'Prerender started', 

    // it can be possible that client bundle starts building before server restarted,
    // prevent that via webpack-dev-middleware api method:
    beforeRestart: devMiddleware.waitUntilValid,

   // custom options for nodemon
   nodemon: {
        // path to server script
        // nodemon-middleware will restart it after each webpack compilation
        script: PRERENDER_SCRIPT_PATH,

        execMap: {
            js: 'node'
        },
        args: [
            '--port', PRERENDER_PORT,
            '--host', PRERENDER_HOST
        ],
        env: {
            NODE_ENV: 'development'
        }
    }
});


const app = express();
app.use(devMiddleware);
app.use(nodemonMiddleware); // request will be delayed if server bundle is in progress
app.use(proxyMiddleware);   // proxying request to server process after server bundle is complete 

app.listen(DEV_SERVER_PORT);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment