Webpack 4 enables dead code elimination from ES6 modules which declare themselves as side-effect free via "side-effects": free in package.json.
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:

vs

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

vs

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?
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.
Hi, thanks for the detailed issue! It seems that you are looking at the "stat" size for webpack-bundle-analyzer. Could you try showing the "parsed" size or "gzip" size instead? Those will display the sizes of what is actually in your bundle.
Set defaultSizes option for the plugin to "parsed" or "gzip" and you should see those sizes when the UI starts up. You can also change between different sizes by moving your cursor to the left side of the UI and selecting a different size from the sidebar.
@valscion Hello, absolutely.
Here is the screenshot of webpack bundle analyzer for individual import with gzip sizes:
https://i.imgur.com/mmeOxk9.png
And here's for destructuring import with gzip sizes:
https://i.imgur.com/cWSLxU0.png
Parsed sizes look very similar:
indivudal import: https://i.imgur.com/kcqN2C1.png
destructuring import: https://i.imgur.com/RqWsvxj.png
It seems that the tooltips in your example only show "stat size", which implicates that the Gzip and parsed size calculation failed for some reason. That is why you're seeing wrong sizes
@valscion My bad, I can see the gzip and parsed sizes when I hover something outside of node_modules.
Individual import:

Destructuring import

I believe parsed size is accurate.
If I change the code to use a few other functions imported from "ramda/es" like this:
import { add, concat, compose } from "ramda/es";
document.write(compose(concat("Hello"), add(2))(3));
This makes bundle size grow from 3.22 KB to 13.4 KB as reported by webpack, and parsed size reported by webpack-bundle-analyzer changes from 3.22 KB to 13.35 KB as expected.
This change however is not reflected in the grid drawn by webpack-bundle-analyzer. If I'm interpreting it correctly, it still shows as if all modules from "ramda/es" have been included into the bundle.
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.
@valscion Thank you for looking into this issue.
I don't see significant change in webpack-bundle-analyzer's graph after disabling optimization.concatenateModules. It does increase the bundle size from 3.22 KB to 53 KB.
It also makes source-map-explorer show graph similar to webpack-web-analyzer, which includes all of the modules which I expected to be omitted.

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?
@evilebottnawi ?
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 :smile:
Because of such issues in v3 we decided to hide contents of concatenated modules by default.
But for folks like me who still thinks that this information is very valuable we've added a checkbox in the sidebar that allows to show it.
However it doesn't solve the root of the problem so I'm leaving this issue opened.
So I don't get does WBA display all the code parts regardless tree shaking that actually take place?
@whitecolor when toggled, WBA shows the contents of the tree-shaken modules. However, there might be modules that have been shaken away as they were not used, but webpack does not tell us which of those modules have been removed.
@valscion Thanks) can you advice what is the best way to check if tree shaking (for particular module) actually works?
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.
@whitecolor You can use the tool source-map-explorer to check what is inside your bundle. One of the screenshot above is generated with this tool. It's running directly on the outputted bundle so you won't have issue with code that is present or not.
@samouss thanks, didn't know about it)
@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.

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:


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.
@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.
@valscion
Why do I see only stats in Treemap Sizes?

Its WP4, dev server, hmr. I even set {defaultSizes: 'gzip'} but it doesn't show.
dev server, hmr
This is why — you need to build the output files to see parsed and gzip sizes. Documentation PR for this confusing behavior would be appreciated 😜
Is it written somewhere inside the doc?
I don't know, most likely not. Documentation contributions would be very welcome :relaxed:
@valscion so I have removed from build options hmr, devserver, devtool from config and result is the same: I see, only Stats. Any advice what else should be turned on/off?
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.
Is there an update on this issue?
This issue has been solved with hiding the concatenated module contents by default. If you have a specific issue @davwheat please open a new issue :relaxed:
Most helpful comment
@samouss do note that the output from
source-map-explorerwill be similar towebpack-bundle-analyzerwhen the checkbox "Show content of concatenated modules (inaccurate)" is not toggled.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.