webpack-bundle-analyzer: Webpack bundle analyzer does not accurately represent effects of tree-shaking in webpack 4

Issue description

Webpack 4 enables dead code elimination from ES6 modules which declare themselves as side-effect free via "side-effects": free in package.json.

Source

I tested this feature on an example project which I published here: https://github.com/mpontus/tree-shaking-example

I attempted to verify that I can use destructuring import from ESM index file without significant increase in bundle size in comparison to importing modules indivudally.

In other words, the following code:

import add from "ramda/es/add";

document.write(add(2, 3));

Should be equavalent to:

import { add } from "ramda/es";

document.write(add(2, 3));

Judging by the bundle size, this assumption is correct:

        Asset      Size  Chunks             Chunk Names
    bundle.js  1.29 KiB       0  [emitted]  main

vs

        Asset      Size  Chunks             Chunk Names
    bundle.js  3.22 KiB       0  [emitted]  main

Small increase in bundle size can be attributed to webpack artifacts, and their portion in the total size will be insignificant in a real project.

I used source-map-explorer to confirm this, and it shows no significant difference:

source-map-explorer-individual.png

vs

source-map-explorer-destructuring.png

Webpack bundle analyzer, on the other hand, does not make it obious that any tree shaking has taken place.

webpack-bundle-analyzer-individual.png

vs

webpack-bundle-analyzer-destructuring.png

The reported stat sizes for ramda module are 2.06 KB and 311.77 KB respectively.

Is it possible to make webpack-bundle-analyzer give better representation of the effects of tree-shaking on the bundle?

Technical info

  • Webpack Bundle Analyzer version: 2.11.1
  • Webpack version: 4.1.1
  • Node.js version: 8.9.4
  • npm/yarn version: yarn 1.3.2
  • OS: Linux

Debug info

How do you use this module? As CLI utility or as plugin?

As a plugin

If CLI, what command was used? (e.g. webpack-bundle-analyzer -O path/to/stats.json)

webpack --mode production

If plugin, what options were provided? (e.g. new BundleAnalyzerPlugin({ analyzerMode: 'disabled', generateStatsFile: true }))

none

What other Webpack plugins were used?

UglifyJsWebpackPlugin

It would be nice to also attach webpack stats file.

https://bpaste.net/show/311b6f843012

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 25
  • Comments: 27 (12 by maintainers)

Most upvoted comments

@samouss do note that the output from source-map-explorer will be similar to webpack-bundle-analyzer when the checkbox “Show content of concatenated modules (inaccurate)” is not toggled.

screen shot 2018-11-20 at 9 37 01

Only when this checkbox is toggled, will WBA potentially show tree-shaken module contents. When that checkbox is left untoggled (which is the default), then the output will be accurate.

This is the difference:

Untoggled

screen shot 2018-11-20 at 9 38 39

Toggled

screen shot 2018-11-20 at 9 38 54


Some of the content might’ve been tree-shaken but WBA does not know which of those modules shown there are missing from bundle output. The size of the “index.js + 20 modules (concatenated)” will be accurate in both cases, though.

The thing is actual dead code elimination is done by UglifyJSPlugin, not webpack itself. Webpack just packs and annotates source code in the way that UglifyJS will be able to determine unused code and remove it from the resulting bundle.

This is the reason you see a lot of actually “excluded” modules under concatenated module - webpack concatenated them all but UglifyJS removed all the unused ones.

As for the absence of parsed and gzipped sizes for contents of concatenated modules: that’s because there is just no way we could parse them there. Those child modules of concatenated module don’t have edges anymore - they’re joined together into one large code chunk! So the best thing we can do is just use the information that webpack gives us about list of concatenated modules and their stat (original) sizes.

Actually, we calculate approximate parsed and gzip sizes for those child modules under the hood using this formula: approximateChildParsedSize = childStatSize / concatenatedStatSize * concatenatedParsedSize. But I decided not to show them in the report tooltip because they’re too inaccurate and may cause a lot of confusion.

@sokra do you have any thoughts how we can improve it?

Could you post a new issue and we can debug there? This is getting off-topic for this particular issue

@valscion thanks, I did it, just added bunderAnalizer to my build config, not to modified dev, seems proper output is needed.

@valscion Thanks for the clarifications! Is it written somewhere inside the doc? Because from a user perspective it’s confusing. It took me a while to understand that output of the treemap was not completely accurate depending on the options selected. I was mainly using this view to understand which modules were tree-shaken or not.

Look at the resulting bundle code. Try running the minified code through prettier for example, and see if the module you wanted removed is indeed removed.

There might be other tools for that, but I haven’t had the need to check that. I’m happy that my bundle sizes are smaller and I can see that a large swath of one huge module has been tree-shaken from our application by just seeing the size differences.

Thanks for explaining this out to us, @mpontus. The current behavior is indeed confusing and unoptimal. I’m not sure if there’s much we can do, as @th0r said in his first comment, but at least we’re now on the same page about this issue 😄

Ah right, yeah this is because of the (concatenated) part you’re seeing. Seems like the heuristic implemented by @th0r in #158 does not take into account tree-shaking that has happened.

Could you try specifying optimization.concatenateModules setting in your webpack config as false and then try again? It will hide the internal parts of the concatenated modules, so you will get similar looking graph as you get with source-map-explorer.