DIY Webpack Dev Server with Express

The best way to build a new project with Webpack is to run the webpack-dev-server for local development. Its turnkey solution to hot reloading is fantastic. But what exactly is the webpack-dev-server? In this episode we'll rebuild it from the ground up using an express server and middleware.

A custom Node server like Express or Koa can give us flexibility to add further optimizations and customizations.

To begin, you may start from where we left off, or clone this git repo and checkout the right branch like so.

git clone git@github.com:lawwantsin/webpack-course.git
cd webpack-course
git checkout express1
npm install

We're going to remake the dev server as part of a larger plan for our web server. This step is crucial to the rest of the course. It's time to graduate from the premade binary to gain more control over our stack.

We'll start by adding Express.

npm install express
mkdir src/server
touch src/server/express.js src/server/main.js

In package.json add a new command to your scripts object.

  "scripts": {
    ...
    "dev": "node src/server/main.js"
  },

This will run the server whenever we type npm run dev.

In src/server/main.js

require("babel-core/register")
require("./express.js")

What babel-core/register does is it tells babel to process the code that follows using babel's rules. In this case we want to use ES6 imports in our server side code.

In src/server/express.js

import express from "express"
import path from "path"

const server = express()
const staticMiddleware = express.static("dist")

server.use(staticMiddleware)

const PORT = 8080
server.listen(PORT, () => {
  console.log(`Server listening on http://localhost:${PORT}`)
})

If we run npm run dev we see the output Server Listening on http://localhost:8080 in the terminal.

Oops, we've got no html now. That's because webpack's not building our main.js anymore. Let's do it manually.

npm run build

Reload the browser and you'll see we've got styles back.

This is almost usable in production as is. But in development, we want the same live-reloading experience in express. So let's add that middleware right next to the static middleware.

In terminal:

npm install webpack-dev-middleware

In express.js right above the staticMiddleware declaration.

...
const isProd = process.env.NODE_ENV === "production"
if (!isProd) {
  const webpack = require("webpack")
  const config = require("../../config/webpack.dev.js")
  const compiler = webpack(config)

  const webpackDevMiddleware = require("webpack-dev-middleware")(
    compiler,
    config.devServer
  )
  server.use(webpackDevMiddleware)
  console.log("Middleware enabled")
}

const staticMiddleware = express.static("dist")
...

Note: Make sure you place the webpackDevMiddleware before the staticMiddleware.

We're only using webpack-dev-server in development anyway, so by wrapping it in an isProd we can keep our production server in the same file.

npm run dev

To restart the webpack dev server and if we change main.css we can see that webpack automatically rebuilds. But we still have to reload the page to see results. Who wants that? Luckily, it's just 1 more additional piece of middleware and we have hot module reloading.

npm install -D webpack-hot-middleware

Change 2 lines to add a whole bunch of websocket functionality to update live.

const webpackHotMiddlware = require("webpack-hot-middleware")(compiler)
server.use(webpackDevMiddleware)
server.use(webpackHotMiddlware)

There are 3 changes to your webpack.dev.js:

  1. At the top add:
import webpack from "webpack"
  1. In the devServer object add the hot option:
hot: true
  1. Add 2 plugins below the modules object:
plugins: [
  new webpack.HotModuleReplacementPlugin(),
  new webpack.NamedModulesPlugin()
]

We'll get more into plugins soon. For now we can think of them as additional processing that happen at the end of the compilation before output.

One last thing. We need to add a new file to src/main.js. It should now look like this:

require("babel-runtime/regenerator")
require("webpack-hot-middleware/client")
require("./main.css")
require("./images/link.jpg")
require("./index.html")

Now when we restart the server, we see that HMR is enabled. In the Network Tab of our DevTools:

Network Tab

Make changes to the main.css file and see it updated live in the browser without a refresh.

In Sum

In this article we traded the off-the-shelf webpack dev server for a custom express dev server. In so doing we basically dissected how the command-line tool does it.

git checkout express1-final

Next Up

Our goal is, as always to create an incredible developer's experience and in the next article we'll work on the server side to finish up our Hot reloading effort.