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
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
You can wrap the load using React. lazy, like so: const RemoteButton = React. lazy(() => import("app2/Button"));
One of the most impactful techniques to reduce the bundle size of a React application is compression. compression is a process in which the size of a file is reduced by re-encoding the file data to use fewer bits of storage than the original file.
To optimize React rendering, you need to make sure that components receive only necessary props. It will let you control the CPU consumption and avoid over-rendering unnecessary features. The solution is to create a functional component that will collect all props and redistribute them to other components.
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
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
):
WMF
allows you to develop independent packages that can easily depend on (and lazy load) each other.Without WMF, webpack allows none of the above.
app2
provides a component Button
app1
consumes it.app1
requests a component using dynamic import
.const RemoteButton = React.lazy(() => import("app2/Button"));
useEffect
, or a Route.render
callback etc.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>
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:
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
").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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With