Development Server

Pipcook - Bridging JavaScript with Python for machine learning - Interview with Wenhe Li

When developing a frontend without any special tooling, you often end up having to refresh the browser to see changes. Given this gets annoying fast, there's tooling to remedy the problem.

The first tools on the market were LiveReload and Browsersync. The point of either is to allow refreshing the browser automatically as you develop. They also pick up CSS changes and apply the new style without a hard refresh that loses the state of the browser.

It's possible to setup Browsersync to work with webpack through browser-sync-webpack-plugin, but webpack has more tricks in store in the form of a watch mode, and a development server.

Webpack watch mode#

Webpack's watch mode rebuilds the bundle on any change of the project files. To activate it, pass the --watch switch to webpack:

npm run build -- --watch

Although this solves the problem of recompiling your source on change, it does nothing on the frontend side and browser updates. That's where further solutions are required.

webpack-dev-server#

webpack-dev-server (WDS) is the officially maintained development server running in-memory, meaning the bundle contents aren't written out to files but stored in memory. The distinction is vital when trying to debug code and styles.

If you go with WDS, there are a couple of relevant fields that you should be aware of:

  • devServer.historyApiFallback should be set if you rely on HTML5 History API based routing.
  • devServer.contentBase - Assuming you don't generate index.html dynamically and prefer to maintain it yourself in a specific directory, you need to point WDS to it. contentBase accepts either a path (e.g., "build") or an array of paths (e.g., ["build", "images"]). The value defaults to the project root.
  • devServer.proxy - If you are using multiple servers, you have to proxy WDS to them. The proxy setting accepts an object of proxy mappings (e.g., { "/api": "http://localhost:3000/api" }) that resolve matching queries to another server. Proxying is disabled by default.
  • devServer.headers - Attach custom headers to your requests here.
The official documentation covers more options.
To integrate with another server, it's possible to emit files from WDS to the file system by setting devServer.writeToDisk property to true.
You should use WDS strictly for development. If you want to host your application, consider other solutions, such as Apache or Nginx.
WDS depends implicitly on webpack-cli in command line usage. Make sure you have both installed.

webpack-plugin-serve#

webpack-plugin-serve (WPS) is a third-party plugin that wraps the logic required to update the browser into a webpack plugin. Underneath it relies on webpack's watch mode, and it builds on top of that while implementing Hot Module Replacement (HMR) and other features seen in WDS.

WPS also supports webpack's multi-compiler mode (i.e., when you give an array of configurations to it) and a status overlay.

Given webpack's watch mode emits to the file system by default, WPS provides an option for webpack-plugin-ramdisk to write to the RAM instead. Using the option improves performance while avoiding excessive writes to the file system.

Learn more about HMR in the Hot Module Replacement appendix. Applying it won't be necessary to complete the tutorial, though.
WPS requires an Active LTS version of Node to work. The attached client scripts have been written with modern browsers in mind (async/await). It's possible to transpile them for older browsers, though.

Getting started with webpack-plugin-serve#

To get started with WPS, install it first:

npm add webpack-plugin-serve --develop

To integrate WPS to the project, define an npm script for launching it. To follow npm conventions, call it as start like below:

package.json

{
  "scripts": {
"start": "wp --mode development",
"build": "wp --mode production" }, ... }
wp stands for webpack-nano. If you are using webpack-cli or another option, adjust the script to your liking.

In addition, WPS has to be connected to webpack configuration. In this case we'll run it in liveReload mode and refresh the browser on changes. We'll make it possible to change the port by passing an environmental variable, like PORT=3000 npm start:

webpack.config.js

const { mode } = require("webpack-nano/argv");
const {
  MiniHtmlWebpackPlugin,
} = require("mini-html-webpack-plugin");
const { WebpackPluginServe } = require("webpack-plugin-serve");

module.exports = {
  watch: mode === "development",
  entry: ["./src", "webpack-plugin-serve/client"],
  mode,
  plugins: [
    new MiniHtmlWebpackPlugin({
      context: {
        title: "Webpack demo",
      },
    }),
    new WebpackPluginServe({
      port: process.env.PORT || 8080,
      static: "./dist",
      liveReload: true,
      waitForBuild: true,
    }),
  ],
};

If you execute either npm run start or npm start now, you should see something similar to this in the terminal:

> wp --mode development

⬡ webpack: Watching Files
⬡ wps: Server Listening on: http://[::]:8080

⬡ webpack: Hash: 6135f8cbee061c80be05
  Version: webpack 4.44.1
  Time: 108ms
  Built at: 08/21/2020 9:54:33 AM
       Asset       Size  Chunks             Chunk Names
  index.html  198 bytes          [emitted]
     main.js   63.4 KiB    main  [emitted]  main
  Entrypoint main = main.js

The server is running, and if you open http://localhost:8080/ at your browser, you should see a hello:

Hello world
Hello world

If you try modifying the code, you should see the output in your terminal. The browser should also perform a hard refresh so that you can see the change.

dotenv allows you to define environment variables through a .env file. dotenv allows you to control the host and port setting of the setup quickly.
Enable the historyFallback flag if you are using HTML5 History API based routing.
If you want even better output, consider error-overlay-webpack-plugin as it shows the origin of the error better.

Accessing development server from the network#

To access your development server from the network, you need to figure out the IP address of your machine. For example, using ifconfig | grep inet on Unix, or ipconfig on Windows. An npm package, such as node-ip, come in handy as well.

On Windows you need to set your HOST to match your IP to make it accessible. Example: HOST=<ip goes here> npm start.

Making it faster to develop webpack configuration#

WPS will handle restarting the server when you change a bundled file. It's oblivious to changes made to webpack configuration, though, and you have to restart the WPS whenever you change something. The process can be automated as discussed on GitHub by using nodemon monitoring tool.

To get it to work, you have to install it first through npm add nodemon --develop, and then add it to your start npm script:

package.json

"scripts": {
  "start": "nodemon --watch webpack.* --exec \"wp --mode development\"",
  "build": "wp --mode production"
},

Polling instead of watching files#

Webpack's file watching may not work on certain systems, for example on older versions of Windows and Ubuntu.

Polling is almost mandatory when using Vagrant, Docker, or any other solution that doesn't forward events for changes on a file located in a folder shared with the virtualized machine where webpack is running. vagrant-notify-forwarder solves the problem for macOS and Unix.

For any of these cases, polling is a good option:

webpack.config.js

module.exports = {
  watchOptions: {
    // Delay the rebuild after the first change (in ms)
    aggregateTimeout: 300,
    // Poll using interval (in ms, accepts a boolean too)
    poll: 1000,
    // Ignore node_modules to decrease CPU usage
    ignored: /node_modules/,
  },
};

The setup is more resource-intensive than the file watching, but it's worth trying out if the file watching doesn't work for you.

Integrating with servers using middlewares#

In case you are developing your entire project against a Node server without a separate frontend build, then one option is to run webpack using a middleware:

There's also a Node API if you want more control and flexibility.

Watching files outside of webpack's module graph#

By default webpack only watches files that your project depends on directly, for example, when you are using MiniHtmlWebpackPlugin and have customized it to load the template from a file.

I've created the webpack-add-dependency-plugin that makes webpack watch additional files.

Development plugins#

The webpack plugin ecosystem is diverse, and there are a lot of plugins that can help with development:

Conclusion#

WPS and WDS complement webpack and make it more developer-friendly.

To recap:

  • Webpack's watch mode is the first step towards a better development experience. You can have webpack compile bundles as you edit your source.
  • WPS and WDS refresh the browser on change. They also implement Hot Module Replacement.
  • The default webpack watching setup can be problematic on specific systems, where more resource-intensive polling is an alternative.
  • WDS can be integrated into an existing Node server using a middleware, giving you more control than relying on the command line interface.
  • WPS and WDS do far more than refreshing and HMR. For example, proxying allows you to connect it to other servers.

In the next chapter, you'll learn to compose configuration so that it can be developed further later in the book.

Previous chapter
Getting Started

This book is available through Leanpub (digital), Amazon (paperback), and Kindle (digital). By purchasing the book you support the development of further content. A part of profit (~30%) goes to Tobias Koppers, the author of webpack.

Need help?