Webpack is an amazing tool you should probably learn to use if you haven't already started. In theory, webpack sounds great, bundling all your code and assets, and using what the cool kids use. I'm using it to set up convenient development and production builds on various projects. It's actually really easy to use once you get the hang of it. But when you have to actually sit down and write those configurations in the beginning, things can get a just a little confusing.
When I started with webpack, I tried to copy/paste random configurations from the internets together into some Frankenstein configuration until something worked. Then, I would never touch the configuration again and hope I never needed anything more from webpack and nothing broke. At some point, I decided life was too short to continue living in fear and confusion. Webpack 2 was recently released, and I resolved to reach some enlightenment.
Webpack Middleware
I've used webpack-dev-server before, and I still love using it. But I was working on configuring isomorphic-relay-router on a little project and was curious about what a hot-loading dev setup would look like with webpack. I needed a finer control than webpack-dev-server offered in this situation, so I delved into webpack middleware.
As a disclaimer, you probably shouldn't be using server-side rendering in your development setup. It's really only useful in a production environment and can introduce unnecessary overhead during development.
The Details
Check out the code on GitHub.
- Install these dependencies: webpack-dev-middleware, webpack-hot-middleware, style-loader, and cross-env.
- webpack-dev-middleware will automatically rebuild the bundle and reload the app when necessary
- webpack-hot-middleware enables hot module reloading for modules that you opt-in
- style-loader allows the css to do HMR, too. (The ExtractTextPlugin should only be used for production, and it doesn’t know how to do hot reloading.)
- cross-env will make sure the proper environment variable is set, regardless of the operating system
- Tweak the webpack config a little. The entry should be an object with an array, and css should only use the ExtractTextPlugin in production mode.
- We can simplify the start script in package.json. Remove webpack from the script and add a flag to ensure nodemon only watches the server instead of everything.
"start": "cross-env NODE_ENV=development nodemon --exec babel-node -w server server"
- Add the webpack middleware to server/index.js after the /graphql endpoint, but before the static and isomorphic endpoints. At this point, if you run npm start, your application should have the [HMR] connected message in the console and automatically rebuild and reload when there are any changes. However, you may see that warning message about triggering a full reload because your modules don’t know how to hot-reload themselves.
if (process.env.NODE_ENV === 'development') {
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const webpackConfig = require('../webpack.config');
const config = webpackConfig;
config.devtool = 'source-map';
config.entry.app.unshift('webpack-hot-middleware/client? reload=true');
config.plugins.push(new webpack.HotModuleReplacementPlugin());
const compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, { noInfo: true }));
app.use(webpackHotMiddleware(compiler));
}
- In order to actually get hot module replacement (HMR) working, we have to manually opt-in the modules we want to hot-load. This means we’re going to just teach routes how to hot-reload itself in app/app.js. In order to be able to re-render the hot updates, we need to move the rendering logic into a function that we can call again.
const rootNode = document.getElementById('root');
const renderApp = () => {
// must require routes like this here in order for hmr to work
const routes = require('./routes').default;
// eslint-disable-line global-require
match({ routes, history: browserHistory }, (error, redirectLocation, renderProps) => {
IsomorphicRouter.prepareInitialRender(environment, renderProps).then((props) => {
ReactDOM.render(, rootNode);
});
});
};
// initial render
renderApp();
// if hot module replacement is running, re-render accordingly
if (module.hot) {
module.hot.accept('./routes', () => {
ReactDOM.unmountComponentAtNode(rootNode);
renderApp();
});
}
- Check that it’s working. You should see [HMR] connected in the console while the app is running. Hot module replacement should work for both js and css updates in this setup, and the page will automatically reload when necessary to show any changes.
Coda
Webpack configuration can be intimidating at first, but it's really not too bad after you spend a little time with it. The basics are pretty simple, but you can do a lot once you get into the details. This example doesn't contain the full set of loaders and configurations I would include in a larger project, but project needs vary. This is usually the most basic set of configurations I start with when setting up a project. If you'd like to learn more about the isomorphic-relay-router setup behind this example, checkout my article on Universal Relay. Also, if you'd like more general information about the entire starter kit project in this example, check out my colleague's article on Strategies for Creating a Starter Kit.