Analyzing Your Webpack Bundle Like a Pro

Photo by CHUTTERSNAP on Unsplash

Analyzing Your Webpack Bundle Like a Pro

Welcome to today’s article, where I’m excited to share a few tips on how to analyze your application’s JavaScript bundle. Specifically, I’ll guide you through identifying the reasons behind bundling certain dependencies. Let’s dive in from the very beginning.

Recently, I was deep in debugging our fairly large React application that we have here at Outreach when I noticed some unusual behavior from React Developer Tools. Upon investigating further, I discovered that React Developer Tools was attempting to connect to a different React renderer. Which renderer, you might wonder? It turns out, it was attempting to connect to the renderer originating from react-test-renderer.

To put it simply, due to certain race conditions, it was randomly selecting either our main React renderer or the TestRenderer. This behavior was definitely unexpected — after all, the TestRenderer should only be utilized in testing scenarios. So, who was importing it? And why?

To start with, I verified that indeed we had bundled react-test-renderer as part of our production bundle. This raised several concerns, not least of which was the additional 28 Kb of download, parsing, and JavaScript execution burden imposed on our users.

As part of our CI pipeline, we utilize the Webpack Bundle Analyzer to generate outputs. With this tool at hand, I could easily use its built-in search feature to confirm my suspicions. Snap!

Webpack Bundle Analyzer — generated HTML with highlighted react-text-renderer dependency

Now that we’ve identified the issue, the next step is to uncover how this undesired dependency made its way into the bundle. You might be tempted to simply use the search function in your IDE, and if you’re lucky, you might stumble upon its usage in App.tsx. However, in most cases, it’s not so straightforward. Our monorepo contains over 1 million lines of TypeScript code, which made me quickly realize that a more sophisticated approach was needed. 🤓

Enter Statoscope. All we require are the stats data from Webpack. The process is relatively straightforward (although in our case, we had to chain this command with node and — max-old-space-size to avoid running out of memory — a challenge we managed to overcome 😀).

webpack --json stats.json

Once we possess our stats.json file (ours was a whopping 2.1 GB), it’s a simple matter of dragging and dropping it into Statoscope — rest assured, it’s a local-first application and no data is uploaded, so don’t worry it takes just a few seconds. With Statoscope, we can then examine why the particular module was required.

Statoscope is providing an explanation of what’s importing our the module

In our scenario, the root cause was clear: react-test-renderer was being required (as part of React Testing Library) within a util file that was utilized by both tests and app code. Because the code wasn’t effectively tree-shaken (given that it’s a third-party dependency and Webpack, which plays it safe, can’t be sure it doesn’t consist of side effects), it unintentionally became part of the bundle itself.

After fixing the issue by simply splitting the file into two where the one required by the app was not requiring this dependency, React Developer Tools was finally happy again! And as a nice bonus, we shaved off 28 Kb of JavaScript.

P.S: Statoscope also provides CLI tools that can seamlessly integrate into your CI/CD system, ensuring that no such unwanted dependencies make their way into the production bundle. This serves as a valuable safeguard to prevent such occurrences in the future.