How to cut your Webpack build time in half

Or a story about our struggle with migrating Create-React-App based app to Webpack v4 & Babel v7

Adriana Olszak
the-stepstone-group-tech-blog
6 min readJun 12, 2019

--

This article is based on our experience of performing migration to Babel 7 and Webpack 4 on a macOS based system. Windows migrations may differ.

What we achieved

Thanks to a simple package upgrade that you can follow along with this article, we managed to cut our build time by half!

We’ve gone from 56,5 seconds to 23 seconds which is a huge improvement taking into account that during migration process we did not implement any optimization techniques.

Our biggest issue is still the time of postcss processing, due to a quite broad usage of plugins that allow smooth css-modules experience. In our tech road-map for the near future we have migration to styled-components planned. This migration is likely to shrink our build time much more.

Migration to Webpack v4

Back in the days when all this started we have chosen create-react-app in version 1 as a base for the creation of our application. From my point of view it was a good decision. It’s a robust and nicely written Webpack configuration with mostly all of useful features prepared to use. It provided us with a good base for our application. We enhanced CRA with RxJS, Redux, CSS Modules. Also, due to very specific needs of our business we needed to yarn eject configuration and modify it by hand to add e.g. dynamic resolution of styles, modules and API configurations.

With this in mind you can treat this article as a guide on how to migrate Webpack v3 configuration based on Create React App v1 to Webpack v4.

Upgrading the packages

First of all we needed to upgrade packages that we were using. Easiest way to do it, is to use yarn command: yarn upgrade-interactive --latest which helps to identify and upgrade outdated packages to their latest available versions. The-latest flag ignores ranges specified in the package.json file so use it with caution!

We’ve also found migrating to the new Webpack a good opportunity to update most of our build, development and maintenance related packages. We’ve updated, among others:

  • plugins to postcss which is handling our CSS Modules setup,
  • jest and all that’s connected to its packages,
  • all of the webpack loaders that we used:style-loader, url-loader and file-loader.

The most important update was the webpack itself, including webpack-dev-server and CRA’s react-dev-utils, as we still wanted to profit from achievements of this great community.

One command to set them all

Webpack v4 introduced new configuration key: mode, which uses its own built-in optimizations accordingly, in the meaning of either the ones that work best for production, or other environments. Introduction of this configuration option took away the burden of manually setting a lot of those optimizations manually, which was previously required to even start using Webpack.
In our webpack.config.dev.js we needed to set the mode to development and for the sake of consistency, as either way mode value defaults to production, we also set mode: 'production' in webpack.config.prod.js.

New packages to be installed

In Webpack v4 usage of uglifyJS was dropped in favor of Terser, due to uglifyJS problems with ES6. As a consequence of that change, we needed to install terser-webpack-plugin as well as mini-css-extract-plugin and optimize-css-assets-webpack-pluginwhich took place of ExtractTextPlugin to handle CSS optimisation: yarn add terser-webpack-plugin mini-css-extract-plugin optimize-css-assets-webpack-plugin

Setup of new packages

To enhance default optimization configuration that mode: 'production' added for us we needed to use and pass down correct settings to new packages that we installed before.

These settings are based on Create React App v2.

Proxy Setup

I imagine that most of us have a need for a proxy when running apps via Webpack Dev Server. It’s useful to allow for cross origin requests when you are hosting your react app locally. We’ve set up an advanced proxy from Create React App v1 to redirect requests to cross origin routes and to do so, we’ve created a proxy configuration file named setupProxy.js. As of Create React App v2 the configuration format has changed.
Old format was an objectified configuration that was then processed by prepareProxy function from webpackDevUtils which was then calling http-proxy-middleware under the hood with the configuration that we defined. In new CRA the advanced usage was stripped from abstraction layer and we can now use http-proxy-middleware directly.

Old ‘advanced proxy’ format looked like:

This configuration file can be used with react-scripts — package that is used by Create React App to gather multiple scripting tools. Direct usage of http-proxy-middleware in setupProxy.js for new CRA version:

Server side render configuration

As our React app uses SSR and it is served by Node.js, similarly like you would do it with Next.js, we needed to define the correct library target in our webpack.prod.config.js file to be able to access the built bundle both, from the browser and Node.js runtime:

In Webpack v3 that’s all that we needed to be able to identify bundle in Node.js application but webpack v4 has a bug that includes awindow object in the bundle initialization function.
A window object is not something that’s available in Node.js applications so we needed to set up a rewrite of global object that will be used in bundle.

This solution enables our bundle to run correctly in a Node.js application.

Migration to Babel 7

According to Babel’s migration guide we can use babel-upgrade to automate and simplify the process.
So typing npx babel-upgrade --write in your favorite shell should handle most of the work that we need to do to finally use Babel 7 super powers.

This tool will do a great deal of work for us. It removes all babel-preset-stage-* packages from our package.json, and instead adds a package for every single plugin that was contained within the stage preset. This split will allow us to minimize the amount of processing that babel needs to do on our codebase by excluding JS-Next features that are not used.

Another very important and time consuming thing that it handles for us is the prefixing all of the presets with @babel, as most of the plugins and presets were moved to babel’s monorepo and packages location provided for require statement have changed.

The command will also add @babel/preset-env to our package.json and .babelrc files. We will need to handle tweaking the setup of this preset to fit the need of our application on our own. I guess it’s hard to do it automatically ;)

Proper setup of @babel/preset-env

As we can read on the @babel/preset-env page we shouldn’t simply use @babel/env as a preset without a settings object.

We don’t recommend using preset-env this way because it doesn’t take advantage of its ability to target specific browsers.

We should pass options to narrow down pollyfils and level of rewriting of our code to specific browsers and/or Node.js versions that are the target runtime of our application.

Steps to be taken:

  1. Definition of .browserslistrc to be able to share the settings between the tools that we are using, e.g. the postcss loader, as it is using autoprefixer under the hood
  2. Selection of preset-env options that fit your app best

Rewriting of import and export statements from incorrect way to propper ES Modules

In our codebase we misunderstood ES6 export and import syntax which resulted in incorrect usage of export default.

We used to write some of the code as:

Which obviously does not follow ES Modules specification, as import {someName} from 'xyz' is not destructurisation, but an import of a named export. To follow ES Modules specification and to be able to compile via Babel 7 we needed to switch to named exports.

For more information:

That’s all folks

I hope that it will help you resolve all the issues that might hold you back. Thanks for reading, if you have any questions or insights please leave me a comment I’ll be happy to discuss with you.

Read more about the technologies we use or take an inside look at our organisation & processes. Interested in working at StepStone? Check out our careers page.

--

--

Adriana Olszak
the-stepstone-group-tech-blog

I’m a Front-End Developer @StepStone Services. Enthusiast of code, Muay Thai practitioner. Vegan. Nintendo girl.