Customize Webpack and babel configuration with fork of create-react-app to optimize SPA

Problem Statement:

Many of us start our React SPA project with create-react-app script. We make the deployable app using available configuration of CRA on development environment.

But when we run ‘yarn build or npm run build‘ and serve it, the app faces below problems:
-> Takes more time to load page due to large bundle size
-> Third party library is not optimized
-> Contain unused polyfills
-> Classname is not short enough which increases bundle size
-> Contain unused css

We cannot directly modify webpack configuration in CRA. To overcome this problem we have a better solution to fork create-react-app rather than ‘yarn eject‘ because ‘this is a one-way operation. Once you eject, you can’t go back! If you eject  it makes your configuration more complex.

Zoom image.png

Solution To this Madness:

-> Create a Fork

Open up your GitHub repo and fork the create-react-app repo into your local.

Zoom image.png

Zoom image.png

Inside the packages directory, there is a folder called react-scripts. The react-scripts folder contains scripts for building, testing and starting your app. In fact, this is where we can tweak the configuration and create our own custom react script.

-> Tweak the Configuration

 1. Bundle size and loading time improvements
     -> Extract the vendor and the runtime code
               
refer: https://gist.github.com/pranay321/cd2638334d8beaec9b0c020d48839327
        Above code splits a single bundle into app code, dependencies code, and webpack runtime. This allows us to cache them with a higher efficiency – if a developer changes the app code, the user won’t have to re-download dependencies.

    -> Third party library is not optimized.
         
We are using few third party libraries in our project like reactstrap, styled component which increase size of vendor bundle. To make this bundle smaller we need to make use of some babel plugin such as ‘babel-plugin-transform-imports’ which remove unused modules.
        When you import a module from Reactstrap as import { Alert } from ‘reactstrap’, other Reactstrap modules also get bundled into the app and make it larger. For this we need to install babel-plugin-transform-imports npm to strip unused modules and also need to modify  .babelrc file

          { 
               "plugins": [ 
                        [  "transform-imports", 
                             {   "reactstrap": {
                                           "transform": "reactstrap/lib/${member}",
                                           "preventFullImport": true
                                        }
                               }
                         ]
                     ]
           }

         we can use this plugin for lodash,react-bootstrap.

    -> Remove unused polyfills
         
For this we need to add below config in .babelrc

          {  
            "presets":[  
                   [  
                     "env",
                        {  
                          "targets":{  
                             "browsers":[  
                                "last 2 versions",
                                "ie >= 11"
                              ]
                           },
                           "modules":false,
                           "useBuiltIns":true
                         }
                     ]
                  ]
            }

this removes unnecessary polyfills with the help of useBuiltIns: true from babel – preset – env

 2. Reduce css bundle size
     -> Shortening CSS Classnames
         We are using css-modules in our app which is in built in CRA. In this CSS minifier does not control HTML output. So CSS Classnames can get too long. eg:

Zoom image.png

            refer this gist files changes to solve above problem:
https://gist.github.com/pranay321/c01410c2631c0eb26edb32fa1793d537
            I have created a custom react script npm package with all this change and use it in our app to shortening classnames.

Zoom image.png

      -> Remove Unused CSS
When we are building a app, chances are that we use a css framework like Bootstrap, Materializecss, Foundation, etc… But you will only use a small set of the framework and a lot of unused css styles will be included.To solve this problem we use Purgecss. 

Purgecss analyzes your content and your css files. It then matches the selectors used in your files with the one in your content files. It also removes unused selectors from your css, resulting in smaller css files. We have modified our custom react script webpack config as 

            const PurgecssPlugin = require('purgecss-webpack-plugin');

            Inside plugin section Add this:

            isEnvProduction &&
               new PurgecssPlugin({
                  paths: glob.sync(`${paths.appSrc}/**/*`, { nodir: true }),
            }),

This removes unwanted css from files.

These are some possible ways apart from
   -> Dynamic Import
   -> Tree Shaking
   -> Code splitting

I have created a custom react script npm package with all this change.

Refer: @shaadi-response/react-scripts

To use this custom react script just replace ‘react-scripts’ with ‘@shaadi-response/react-scripts’ in package.json of your CRA Application

After using custom react script,  below is the difference in package build:

                           Before                                                                             After

Zoom image.png

Summary:

As you see we have reduce almost 70% – 80% of our CSS chunk size.

References:

-> https://www.purgecss.com
-> https://www.freecodecamp.org/news/reducing-css-bundle-size-70-by-cutting-the-class-names-and-using-scope-isolation-625440de600b/
-> https://github.com/GoogleChromeLabs/webpack-libs-optimizations