Skip to site navigation
Go home

Lee Reamsnyder

Finding the source of webpack bundle bloat

2019 Sep 21 6 min read Published by Lee Reamsnyder Permalink

Let’s say you’re using webpack to build a web application, and you’ve started to notice that your bundles are getting a bit too huge for comfort.

Finding all the files in your bundle

There are a couple of ways to see what modules or files are contributing to the size of your bundle.

The docs for Create React App offer one suggestion called source-map-explorer, which is OK. If you’re already generating source maps, you can use source-map-explorer with zero additional configuration. But I prefer Webpack Bundle Analyzer for a reason that I’ll get to in a bit.

webpack-bundle-analyzer and source-map-explorer tell you what modules are getting into your bundle. Sometimes, that’s all you need!

For example, here’s the default output for webpack-bundle-analyzer for one of my work projects:

An area chart showing the packages in a bundle

In the main App bundle file (App.107d1f6b.chunk.js), you can see that the @material/ui package is a huge chunk, which I would expect because its our primary UI library.

The next biggest module appears to be highcharts, our chart library. I need that for sure, but do I need to render the charts immediately? Maybe it could be lazy loaded to reduce the size of the initial app bundle?

This sort of view might also help you spot a module that is being included, but not actually used. Like if I didn’t know what highcharts was, I could do a quick search through my app’s code for require('highcharts or from 'highcharts' to see if my code is bringing it in.

Sometimes, though, you need to go deeper.

Finding exactly how a file got in your bundle

Other modules that you’re using will have their own dependencies, so you’ll likely see modules in your bundle that make you think, “how on earth did this get in here?”

Here’s where it’s much more useful to understand how they got there, which the aforementioned tools won’t tell you out of the box.

The Webpack Analyzer site, however, will. But it’s not the most intuitive, and you’ve got to jump through some hoops.

To use it, you need to generate a JSON file with your bundle’s profile/statistics information in a very specific format. A lot of other articles make it sound like this was an obvious and simple thing to do, but I had some trouble nailing it down.

If you’re already using the webpack command line tool (like, your build command is just calling "webpack" on the command line), you can make the stats file like so:

webpack --profile --json > stats.json

And it’ll output a file stats.json in the same directory. Done.

If the webpack command isn’t an option for you, you can use Webpack Bundle Analyzer to generate the file.

First, install it:

# using npm
npm install --save-dev webpack-bundle-analyzer
# or yarn
yarn add -D webpack-bundle-analyzer

Next, add it to your webpack configuration:

// ... other plugins/and such
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.exports = {
  // ... probably other configuration bits
  plugins: [
    // ...most likely there are other plugins
    // add these lines after them
    new BundleAnalyzerPlugin({
      generateStatsFile: true,
      openAnalyzer: false, // <-- set true to open the default analyzer app when the build finishes, if you're interested
    }),
  ],
}

Finally, run your build process again. You’ll see a stats.json file in the root of your build’s output directory.

Fair warning: when I’ve done this sometimes with larger bundles, sometimes the stats.json file would be incomplete and therefore useless. Webpack just, I dunno, gave up trying to finish it and spits out half the file. I’m not sure if this is a memory issue or what, but… that’s not great. Annecdotally, a bundle that cracked 500kb (gzip’d) was roughly where I started seeing issues.

Once you have the stats.json file:

  1. Go to the Webpack Analyzer site
  2. If you don’t immediately see "Upload webpack stats", click Open
  3. Use the JSON file selector to select your new stats.json file.
  4. Once everything’s loaded, click Modules

a screen shot of Webpack Analyzer showing a dependency tree image and list of modules

The pretty node-y visualize-y thing up top (a) is not super useful when you have lots of modules and (b) is probably gonna slow your computer to a crawl.

What we’re really interested in is the very large table of files that lists everything in your bundle.

What I like to do here is do an in-browser search for the name of an offending file. When you find it in the table, click the id column to view the details for that file.

screen capture of the module details for React's production module

Now here’s the good stuff. Once you’re looking at the details for a module, the reasons block will tell you all the other modules that imported this file. It could be multiple! It could be a different NPM library! You can click the module column there to go up the dependency tree.

Keep going up and you should eventually find some file you recognize from your project (a good hint is it won’t have node_modules in the file path), and then you’ll know how the offending file got in there.

When you’re looking at the details for a module, you can also see its full source code and all of its dependencies, which might also be useful for sniffing out how a file got in here.

Now, how to actually chop things out—if it’s even possible—is very dependent on your bundle and the libraries you’re using. Here’s some resources I’ve used before:

  • 3 ways to reduce webpack bundle size by Jacob Lind and Possible ways to reduce your webpack bundle size by akintayo shedrack both have a good mix of general principles—like only importing parts of libraries, if possible—and specifics for known-troublesome libraries like moment.
  • If you’re using lodash but you notice that another library is using lodash-es, you can pretty safely alias 'lodash-es': 'lodash', because lodash and lodash-es are identical—just packaged slightly differently—and are interchangable, so you don’t need both.
  • Speaking of lodash, it’s ripe for optimizations, but it isn’t always easy.

Hope that helps!

← Older post “Intl is not available” error in IE 11 → Newer post Why use GraphQL?

Menu

  • Home
  • Blog
  • Work
  • Contact
  • Archives
  • Feeds: RSS | JSON

Search

Elsewhere

  • GitHub
  • Instagram
  • Mastodon
  • Twitter
© Copyright 2006–2023, Lee James Reamsnyder
Back to top