Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
mpontus opened this issue Mar 12, 2018 · 27 comments

Comments

@mpontus
Copy link

mpontus commented Mar 12, 2018

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

@valscion
Copy link
Member

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.

@mpontus
Copy link
Author

mpontus commented Mar 13, 2018

@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

@valscion
Copy link
Member

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

@mpontus
Copy link
Author

mpontus commented Mar 13, 2018

@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.

@valscion
Copy link
Member

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.

@mpontus
Copy link
Author

mpontus commented Mar 13, 2018

@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.

@th0r
Copy link
Collaborator

th0r commented Mar 13, 2018

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?

@alexander-akait
Copy link
Member

@th0r
Copy link
Collaborator

th0r commented Mar 13, 2018

@evilebottnawi ?

@valscion
Copy link
Member

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 😄

@th0r
Copy link
Collaborator

th0r commented Sep 11, 2018

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.

@wclr
Copy link

wclr commented Nov 18, 2018

So I don't get does WBA display all the code parts regardless tree shaking that actually take place?

@valscion
Copy link
Member

@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.

@wclr
Copy link

wclr commented Nov 19, 2018

@valscion Thanks) can you advice what is the best way to check if tree shaking (for particular module) actually works?

@valscion
Copy link
Member

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.

@samouss
Copy link

samouss commented Nov 19, 2018

@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.

@wclr
Copy link

wclr commented Nov 19, 2018

@samouss thanks, didn't know about it)

@valscion
Copy link
Member

@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.

@valscion
Copy link
Member

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.

@samouss
Copy link

samouss commented Nov 20, 2018

@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.

@wclr
Copy link

wclr commented Nov 20, 2018

@valscion
Why do I see only stats in Treemap Sizes?
image

Its WP4, dev server, hmr. I even set {defaultSizes: 'gzip'} but it doesn't show.

@valscion
Copy link
Member

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 ☺️

@wclr
Copy link

wclr commented Nov 20, 2018

@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?

@valscion
Copy link
Member

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

@wclr
Copy link

wclr commented Nov 20, 2018

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

@davwheat
Copy link

Is there an update on this issue?

@valscion
Copy link
Member

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 ☺️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants