I use WebPack and have Tree Shaking setup, I only use a few icons from material-ui-icons
, with imports like like import {Edit, AddBox} from 'material-ui-icons';
and expect the tree shaking algorithm to strip out all the unused icons from the bundle.
Analyzing the contents of my production bundle I see that the entirety of material-ui-icons is used in my bundle, including piles of icons I definitely do not use.
And I manually verified that icons I do not use are present in the bundle.
Looking at the contents of node_modules/material-ui-icons/, I notice that while an index.es.js exists and is being used, the modules being imported themselves appear to be CommonJS modules.
Given the "Use ES2015 module syntax (i.e. import and export)." limitation of tree shaking, I have a feeling that the reason .
It's a side topic but I just realized that the analysis of the Material-UI import looks strange as well. Material UI has an entire material-ui/es/
folder and an index.es.js
that is being imported, but it looks as if all the modules are being imported from the CommonJS modules instead of the modules inside material-ui/es/
.
| Tech | Version |
|--------------|---------|
| Material-UI | 1.0.0-beta.38 |
| Material-UI-Icons | 1.0.0-beta.36 |
| React | 16.2.0 |
| WebPack | 3.11.0 |
| Babel | 6.26.0 |
@dantman I believe that we have already done our best to have bundler tree shaking Material-UI. We have been documentation the alternatives: https://material-ui-next.com/guides/minimizing-bundle-size.
It's a side topic but I just realized that the analysis of the Material-UI import looks strange as well. Material UI has an entire material-ui/es/ folder and an index.es.js that is being imported, but it looks as if all the modules are being imported from the CommonJS modules instead of the modules inside material-ui/es/.
Follow #10212.
@dantman I believe that we have already done our best to have bundler tree shaking Material-UI. We have been documentation the alternatives: https://material-ui-next.com/guides/minimizing-bundle-size.
Sorry, you're right. I did not see the es/ folder available in material-ui-icons
. When I looked at material-ui/es/
my editor's view was wrong and it looked incomplete. So I did not realize that index.es.js
doesn't use es/
and material-ui(-icons)/es
is a way to import things that actually works.
I tried telling WebPack to alias everything to the /es
version and while the size of both material-ui
and material-ui-icons
gets smaller, none of the icons I do not use disappear from the bundle (babel is probably just more efficient at bundling things in my environment).
@dantman Thanks for the feedback. This confirms what I have experienced with webpack so far. Tree shaking has never worked. I have never seen anyone taking advantage of it. We have put "sideEffects": false,
in the package, we are using module imports over commonJS. Maybe we miss something else but without debug information. I don't know what to do.
For posterity I just ran the following tests on my project, by changing the sideEffects
in the package.json
of @material-ui/core
and @material-ui/icons
directly in node_modules
. Bundle sizes uncompressed:
Tests on core
:
import { ... } from '@material-ui/core'
with "sideEffects": true
in core
: 956kbimport { ... } from '@material-ui/core'
with "sideEffects": false
in core
: 790kbimport Foo from '@material-ui/core/Foo'
with "sideEffects": true
in core
: 789kbimport Foo from '@material-ui/core/Foo'
with "sideEffects": false
in core
: 789kbTests on icons
:
import { Bar } from '@material-ui/icons'
with "sideEffects": true
in icons
: 3.87mbimport { Bar } from '@material-ui/icons'
with "sideEffects": false
in icons
: 793kbimport Bar from '@material-ui/icons/Bar'
with "sideEffects": true
in icons
: 789kbimport Bar from '@material-ui/icons/Bar'
with "sideEffects": false
in icons
: 789kbAt first I thought tree-shaking was not working because bundle size are slightly different when using the different import styles, but that difference is negligible (789 -> 790 for core
and 789 -> 793 for icons
), but this is likely due to the code added by Webpack to tree shake. The difference is massive when turning off tree-shaking so it definitely _works_.
I want to confirm that tree shaking works............in production only!
development
bundle, we noticed a gz size of 14mb and a stat size of 80mb, of which the @material-ui/icons packages was 32mb.development
modeHere is the result of aliasing multi repo dependencies to just the local app node_modules, you can see the sheer size of @material-ui/icons accounting for ~30% of the stat size:
This applies to dev only, and I repeat - it has no bearing on a production build. Production builds with tree shaking work just fine when importing from an esm index.
If you want to optimize dev builds:
e.g. replace import { Home } from '@material-ui/icons'
with import Home from '@material-ui/icons/Home'
I hope my few hours of hair pulling helps someone out!
Once again, this has no bearing on production, and only applies to optimizing a dev build.
@rosskevin Better not use aliases here. They don't support the full range of tree-shaking. If you need tree-shaking for development you should try some combination of config flags from https://webpack.js.org/configuration/mode/#mode-productionhttps://webpack.js.org/configuration/mode/#mode-production.
You probably need usedExports: true
and ModuleConcatenationPlugin
as I understood https://webpack.js.org/guides/tree-shaking/#add-a-utility
I need source aliases because, well it's all our source code. In this case our app source imports icons, and our platform monorepo (source aliased) import icons which generated the duplicate inclusion.
Adding a node_module source alias for icons solved the duplicate inclusion but the package still accounts for almost 30% of the memory footprint. I would generally agree not to have to go this direction but I also don't want to add time to the build/rebuild in development mode by using the production optimization. The memory footprint may also not be a big deal to others, but our app is big enough that it made sense for us to optimize while we were at it.
I did use usedExports: true
to no effect, but didn't know I might need the ModuleConcatenationPlugin
. I may look at that and see what the cost is vs what I'm doing now.
There is no doubt I'm in an edge case, but I hope to save someone some time in the future.
Most helpful comment
I want to confirm that tree shaking works............in production only!
TL;DR
Background
development
bundle, we noticed a gz size of 14mb and a stat size of 80mb, of which the @material-ui/icons packages was 32mb.Cause
development
modeHere is the result of aliasing multi repo dependencies to just the local app node_modules, you can see the sheer size of @material-ui/icons accounting for ~30% of the stat size:
Solution
This applies to dev only, and I repeat - it has no bearing on a production build. Production builds with tree shaking work just fine when importing from an esm index.
If you want to optimize dev builds:
e.g. replace
import { Home } from '@material-ui/icons'
withimport Home from '@material-ui/icons/Home'
I hope my few hours of hair pulling helps someone out!
Once again, this has no bearing on production, and only applies to optimizing a dev build.