Webpack Optimizations - Production-ready React App
Tags: webpack, react, javascript, productivity
Published: 2020-07-26 09:53:51

Webpack is a static module bundler for modern JavaScript applications(Eg. React). When webpack processes our application, it internally builds a dependency graph that maps every module our project needs and generates one or **more** bundles.

The code/configs used in this blog are available in [this](https://github.com/Nikhil-Kumaran/reactjs-boilerplate) repo.

A simple webpack.config.js for React application looks like this.
```javascript
const path = require('path');
const HtmlWebPackPlugin = require('html-webpack-plugin');

module.exports = {
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: 'bundle.js',
  },
  resolve: {
    modules: [path.join(__dirname, 'src'), 'node_modules'],
    alias: {
      react: path.join(__dirname, 'node_modules', 'react'),
    },
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
      {
        test: /\.css$/,
        use: [
          {
            loader: 'style-loader',
          },
          {
            loader: 'css-loader',
          },
        ],
      },
      { // If you are not using less ignore this rule
        test: /\.less$/,
        use: [
          {
            loader: 'style-loader',
          },
          {
            loader: 'css-loader',
          },
          {
            loader: 'less-loader',
          },
        ],
      },
    ],
  },
  plugins: [
    new HtmlWebPackPlugin({
      template: './index.html',
    }),
  ],
};
```
![single bundle file](https://dev-to-uploads.s3.amazonaws.com/i/7ar4dqguqpj9k0alvr30.png)

Out of the box, with this above config, the webpack generates one JS bundle file. For large projects/applications, this bundle size becomes very large(in MiBs). So it is essential to split the single bundle into multiple chunks and load them only when needed.

Here's where [lazy loading](https://reactjs.org/docs/code-splitting.html#reactlazy) in React comes in. It's basically importing the component only when needed. Lazy loading components on the route level is a good start.

When we lazy load components, webpack creates multiple bundle files based on our routes, without needing any extra configuration.

![multiple bundles](https://dev-to-uploads.s3.amazonaws.com/i/3vq61wo29o09enjsofgg.png)

We can use hash file names for our bundles, which changes only when we build our app after doing modifications in that particular chunk. So when there is no change, the same hash will be retained and the browser serves those bundle files from the cache. Refer [this](https://webpack.js.org/configuration/output/#outputfilename) for other hash options

```javascript
output: {
  path: path.resolve(__dirname, 'build'),
  filename: '[name].[chunkhash].bundle.js',
}
```
![multiple bundles with hash](https://dev-to-uploads.s3.amazonaws.com/i/zovyc0d2cehxybl6gqvb.png)

It's great that we split our bundles based on routes without any additional configuration in webpack, but still, our main bundle contains all the vendor codes(node_modules). We can add a few configs to tell webpack how we want to further spit the bundles.

```javascript
optimization: {
  splitChunks: {
    cacheGroups: {
      vendors: {
        test: /node_modules\/(?!antd\/).*/,
        name: "vendors",
        chunks: "all",
      },
      // This can be your own design library.
      antd: {
        test: /node_modules\/(antd\/).*/,
        name: "antd",
        chunks: "all",
      },
    },
  },
  runtimeChunk: {
    name: "manifest",
  },
}
```
![multiple bundles with hash and with further vendor chunk](https://dev-to-uploads.s3.amazonaws.com/i/18yq5w8oxmuvza8ngo25.png)

Let's go through the config. `optimization.splitChunks.cacheGroups` is where we define our chunks. Here I used the chunk name `vendors` for all the dependencies in `node_modules` except `antd`([Ant Design](https://ant.design/) is a UI component library) and I used the chunk name `antd` for the Ant design dependency alone. 

The reason why we are making the vendors separate is that, once our project gets matured, we won't be frequently adding any new dependencies, so our chunk filename hash won't be changing for every build and the browser can serve this vendor chunk from the cache.
I separated antd from the vendor chunk because this can be our own design library where we frequently add/update components, so any change in this chunk should not affect our vendor chunk hash.
I also extracted out the manifest which webpack maintains, containing information needed to run our application.

If you noticed the build output, our vendor chunk is highlighted in yellow and marked as [big]. Webpack is configured to warn us if the bundle size is more than 244KiB. We can safely ignore this warning because anyway our bundles should be gzipped and transferred over the network. This gzip encoding is done by default in some of the static file servers like [netlify](https://www.netlify.com), [serve](https://www.npmjs.com/package/serve) and it is easy to configure in others [AWS CloudFront](https://aws.amazon.com/blogs/aws/new-gzip-compression-support-for-amazon-cloudfront)
Anyways, if we want to gzip and tell webpack to use gzipped files for calculation, we can add the below config.

```javascript
const CompressionPlugin = require('compression-webpack-plugin');

plugins: [
  new CompressionPlugin({
    test: /\.js(\?.*)?$/i,
  }),
],
performance: {
  hints: "warning",
  // Calculates sizes of gziped bundles.
  assetFilter: function (assetFilename) {
    return assetFilename.endsWith(".js.gz");
  },
}
```
![multiple bundles with hash and with further vendor chunk and gzipped](https://dev-to-uploads.s3.amazonaws.com/i/xic7ff9bjlfavsccjuad.png)

**To sum up,**
- We set up a minimum webpack config to run our react app.
- We used lazy loading to split our single bundle into multiple chunks.
- We used hash filenames to version our bundle files.
- We spit our main bundle further creating vendor and antd chunks.
- We used gzip to compress our bundles(also need to be done in our static hosting server) to avoid webpack bundle size warnings.

You can see the full webpack configuration [here](https://github.com/Nikhil-Kumaran/reactjs-boilerplate/blob/master/webpack.config.js)

That's it, folks, Thanks for reading this blog. Hope it's been useful for you. Please do comment on your questions and suggestions.

### References:
- https://reactjs.org/docs/code-splitting.html
- https://webpack.js.org/configuration/optimization/
- https://webpack.js.org/plugins/split-chunks-plugin/
- https://webpack.js.org/configuration/performance/