How to reduce react app build time and understanding behaviour of webpack when bundling

9.8k views Asked by At

Recently I am trying to optimize the performance of a web app(React). Assuming it is somewhat heavy as it consists of Code editors, Firebase, SQL, AWS SDK, etc. So I integrated react-loadable which will lazy load the components, After that, I got this Javascript heap out of memory issue.

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory in React

After some research(From a friend), I came to know that If we keep too many lazy loadings webpack will try to bundle them parallelly It might be the cause to get a Javascript heap memory issue, To confirm that I removed all Lazy loading routes in my App and built. Now build is successful. Later as suggested by the community I increased Node heap space size and got the below insights

First I increased it to 8 GB(8192) then build is success I got build time of around 72 mins, From next time onwards I am getting around 20 mins. Then I decreased heap memory size to 4 GB(4096) and getting build success it is around 15 - 20 mins. System configuration is 2vCPU, 16GB RAM(AWS EC2 Instance r5a.large).

Next, I kept build in another system (Mac book pro, i5, 8 GB RAM, 4 Cores) Now it took 30 mins, Second time it took 20 mins

So from these data points, I got a couple of questions

  1. Do we need to keep increasing heap space whenever we add some code? If yes what would be the average heap memory size in the community
  2. What would be the usual configuration for build systems for these kinds of heavy apps, Why because now I am not sure whether to increase the number of cores or RAM or Heap space or Altogether something to do with our app code.
  3. Do webpack provide any kind of solutions to avoid heap memory issue like limiting parallel processes, or any plugins?
  4. If at all it is related with our App code, Is there any standard process to debug where it is taking memory and to optimize based on that

PS : Some people suggested to keep GENERATE_SOUCREMAP=false it got worked but we need source maps as they will be helpful in debugging production issues

2

There are 2 answers

2
Dinesh Chitta On BEST ANSWER

Finally, I could resolve the heap out of memory issue without increasing heap memory space.

As mentioned in the question If I remove all Lazy routes build is getting successful Or I had to keep 4GB Heap space to get it success with plenty of build time. When the build is success with 4GB Heapspace I observed that nearly 8 - 10 chunk files sizes are nearly 1MB. So I analyzed all those chunks using Source map explorer. In all chunks almost same libraries code is included (In my case those are Firebase, Video player, etc which are heavy)

So In my assumption when webpack is trying to bundle all these chunks It had to build all those libraries dependency graph in every chunk which in turn causes heap memory space issue. So I used Loadable components to lazy load those libraries.

After lazy loading all those libraries all chunks files size is reduced almost by half, And Build is getting success without increasing any heap space and build time also got reduced.

After optimization if I keep build in 6vCPU , i7 System it is taking around 3 - 4 minutes and I observed that based on the number of Cores available in the system build time is getting reduced. If I keep build in 2vCPU system it is taking around 20 - 25 mins like that sometimes

0
Domi On

Vanilla webpack has been developed for monolithic builds. It's main purpose is to take many modules and bundle them into ONE (not many). If you want to keep things modular, you want to use webpack-module-federation (WMF):

  1. WMF allows you to develop independent packages that can easily depend on (and lazy load) each other.
  2. These packages will automatically share dependencies between each other.

Without WMF, webpack allows none of the above.

Short Example

  1. A library package app2 provides a component Button
  2. An application package app1 consumes it.
  3. When the time comes, app1 requests a component using dynamic import.
  4. You can wrap the load using React.lazy, like so:
    const RemoteButton = React.lazy(() => import("app2/Button"));
    
    • E.g., you can do this in a useEffect, or a Route.render callback etc.
  5. app1 can use that component, once it's loaded. While loading, you might want to show a loading screen (e.g. using Suspense):
    <React.Suspense fallback={<LoadingScreen />}>
      <RemoteButton />
    </React.Suspense>
    
  • Alternatively, instead of using lazy and Suspense, just take the promise returned from the import(...) statement and handle the asynchronous loading any way you prefer. Of course, WMF is not at all restricted to react and can load any module dynamically.

On the flip side, WMF dynamic loading must use dynamic import (i.e. import(...)), because:

  1. non-dynamic imports will always resolve at load time (thus making it a non-dynamic dependency), and
  2. "dynamic require" cannot be bundled by webpack since browsers have no concept of commonjs (unless you use some hacks, in which case, you will lose the relevant "loading promise").

Documentation

Even though, in my experience, WMF is mature, easy to use, and probably production ready, it's official documentation is currently only a not all too polished collection of conceptual notes. That is why I would recommend this as a "Getting Started" guide.