Improving Webpack Developer Experience

With Create React App's React Developer Utilities

By Michael Chang

January 27th, 2021

image

My Journey

I’ve been using my own Webpack configuration for a long time. But for quick demo projects such as those for Medium articles, using a custom build system made no sense. So I started using Create React App again. All the incredible developer experience that I had long forgotten came back to me — things like allowing me to use a different port if my desired one is in use, using an existing browser tab instead of opening a new one, and automatically rebuilding with newly installed dependencies. All these nice features got me thinking — could I somehow have the same functionality without using Create React App?

React Developer Utilities

React Dev Utils is a library of Create React App’s build tools. Because this is a separate dedicated package, you can use them even if you’re not using Create React App! To install, run npm install --save-dev react-dev-utils. Now let’s dive into some of these tools and see how they can make your life easier.

Choosing Port

Have you had a situation where your desired port is taken? If you have multiple projects running on the same development port, this issue can come up quite often. You’ll get this message

Error: listen EADDRINUSE: address already in use 127.0.0.1:3000

and Webpack Dev Server will fail to start. To fix, you have to either find and kill the other process using your port or restart Webpack Dev Server with a different port.

React Dev Utils provides the WebpackDevServerUtils.choosePort utility to automatically detect if the desired port is in use and select another port.

const { choosePort } = require('react-dev-utils/WebpackDevServerUtils');

const desiredPort = parseInt(process.env.PORT, 10) || 3000;

const config = async () => {
  const port = await choosePort(host, desiredPort);
  if (!port) {
    process.exit();
  }

  // Webpack configuration
  return {
    ...
    devServer: {
      port,
    },
  };
};

module.exports = config;

Instead of using an object configuration, export your Webpack configuration as an asynchronous function. If the desired port is in use, choosePort will block and wait for user input.

Something is already running on port 3000.
Would you like to run the app on another port instead?

If the user types y (for yes), then the utility will automatically find the next available port. If the user types n (for no), then the utility will return null and process will exit. Now, you can resolve port conflict issues gracefully.

Open Browser

After starting Webpack Dev Server, you’ll most likely need to open the browser to the specified development URL. To automatically do this, Webpack Dev Server provides an open flag. But it does this in a dumb way — it always opens a new browser tab with the specified host and port, ignoring any existing browser tabs with that same URL. This leads excess browser tabs open to the same page which also has a side-effect of decreasing performance from multiple hot reloading tabs.

For Chrome users on Mac, React Dev Util’s openBrowser utility can smartly use an existing browser tab.

const openBrowser = require('react-dev-utils/openBrowser');

const host = process.env.HOST || 'localhost';
const port = parseInt(process.env.PORT, 10) || 3000;

module.exports = {
  ...
  devServer: {
    onListening: () => {
      openBrowser(`[http://${host}:${port}`](http://${host}:${port}`));
    },
    port,
    host,
  },
};

Leveraging the onListening hook that is called after Webpack Dev Server starts listening for connections on the specified host port, we can call the openBrowser utility. If a Chrome tab exists for the URL, it will smartly switch to that tab. If not, it will just open a new tab just like the open flag. Now, you will no longer have duplicate and unnecessary browser tabs.

WatchMissingNodeModulesPlugin

Webpack Dev Server’s hot reloading automatically rebuilds when it detects code and dependency changes, but there are cases where this detection fails. Using packages that are not installed is one of those cases. In this situation, Webpack Dev Server will fail to compile as expected, but after the package is installed, it will still not be able to recover. To fix this, you will need to restart Webpack Dev Server. React Dev Util’s WatchMissingNodeModulesPlugin solves this.

const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');

module.exports = {
  ...
  plugins: [
    new WatchMissingNodeModulesPlugin(path.resolve('node_modules')),
  ],
};

WatchMissingNodeModulesPlugin will detect the missing dependencies and watch node_modules. If they appear, the plugin will automatically trigger a rebuild. Now, you will no longer have to restart Webpack Dev Server after using a package that hasn’t been installed.

Final Thoughts

Even if you use your own Webpack configuration, Create React App’s build tools can still improve your developer experience. It provides tooling to resolve inconveniences such as conflicting ports, unnecessary browser tabs, and missing package rebuild failures.

Resources



Continue Learning