webpack: Tree shaking completely broken?
updated with the latest versions (29.06.2017) Webpack - 3.0.0 Babel-preset-env - 1.5.2 Node - 8.1.2 OS X - 10.12.5
Current
Tree shaking is not removing imports, not used in the app.
Expected
Tree shaking, removing not used imports.
The test case is a simple app with React & React Router v4. Link
component is not used, but the whole router package is bundled.
import React from 'react'
import ReactDOM from 'react-dom'
import { Link } from 'react-router-dom'
ReactDOM.render(
<p>Hey</p>,
document.getElementById('app')
)
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Reactions: 62
- Comments: 163 (21 by maintainers)
Commits related to this issue
- Upgrade to Webpack 2.1.0-beta.21 https://github.com/webpack/webpack/issues/2867#issuecomment-240961842 — committed to redmountainmakers/kilntroller-ui by nylen 8 years ago
- add webpack's "sideEffects: false" to package.json Because of webpack/webpack#2867, webpack doesn't remove all unused code when a user imports a single component from the library. 'date-fns' Webpack... — committed to TxHawks/react-powerplug by TxHawks 6 years ago
- add webpack's "sideEffects: false" to package.json Because of webpack/webpack#2867, webpack doesn't always remove all unused code when a user imports a single component from the library. Webpack 4 f... — committed to TxHawks/react-fns by TxHawks 6 years ago
- Add webpack's "sideEffects: false" to package.json (#45) Because of webpack/webpack#2867, webpack doesn't remove all unused code when a user imports a single component from the library. 'date-fns' ... — committed to renatorib/react-powerplug by TxHawks 6 years ago
Tree shaking should occur independently from minification/dead code removal IMO if possible. Otherwise it really doesn’t rely on Webpack, it relies on a different 3rd party tool.
For example, when I run Rollup to produce a bundle, it’s tree shaken without any sort of minification or dead code removal.
This is important for the Ionic Framework team because in order to support Webpack, Closure Compiler and Rollup independently, we run minification/dead code removal as a separate step independent from the bundling process.
Thanks, Dan
Thank you everyone for keeping this issue up to date. I have to apologize for not communicating the update on how we plan to tackle this issue.
So after we have finished our 2.3 regression milestone for bugfixes for webpack 2(.2) final, we start on features.
Therefore I’m going to add this to our feature milestone.
So tl;dr https://github.com/mishoo/UglifyJS2/issues/1261 is the biggest hurdle on solving this problem. So we either have to:
See if implement scope hoisting (rollup features) or build a new general purpose optimizer that supports a level of program flow analysis.
We would really appreciate as much brainstorming as possible to how we could solve this.
I’ve hit the same problem:
index.js
re-export.js
helpers.js
unused-helper.js
unused-helper-2.js
Expected output
The expected output is a bundle with only the following modules: index, re-export and helpers. The unused modules should not be included, as those are only re-exported by re-export.js, but this export is never used.
Actual output
Rollup
As a small comparison, rollup outputs only:
Any update on this? I am surprised to see this not a part of the 2.x milestones. This is a major issue IMO because this is one of the “killer apps” of Webpack 2.x and it doesn’t seem to be working as one would expect for even basic examples.
What can we do to help expedite this?
Thanks, Dan
Because the whole internet is full of articles like “BREAKING NEWS: WEBPACK 2 BRINGS ES6 TREE SHAKING. FIND OUT MORE IN THIS ARTICLE” most of which were written long before Webpack 2 was released and it became obvious that Webpack 2 itself doesn’t actually tree-shake.
You mean articles like this one in the official docs?
Since webpack is 2.6 now and tree-shaking is still not working, is there any news on this? @sokra
@Rich-Harris Thanks for chiming in here, it’s a huge help to hear your perspective.
Is there any way to turn this back on? If not, I’d be curious to know the last version of Rollup that does this.
Would it be possible to add a “blacklist” of non-tree-shakeable modules to the bundler config (as suggested above by @arackaf)? Also, it would be super useful to have some examples of modules in the wild that break this expectation.
@arackaf 👍 for the clearly expressed idea:
Here’s a straw man proposal for this might look in the Rollup / Webpack config:
Summarizing the expected semantics from earlier discussions, this configuration would specify:
assumePure: true
Assume that all modules are pure (without side effects) EXCEPT modules that are imported as “obviously with side effects”, e.g.import "d3-transition";
orimport 'rxjs/add/operator/catch';
.impurePackages:[ ... ]
Specify a list of modules that expose named or default exports AND have side effects. These modules would need to be 100% included in the resulting bundle if they are imported by any module that has any exports that are imported into the root module for the bundle, conforming to the current inclusion behavior of Rollup (and perhaps Webpack too at the moment), “erring on the side of caution”.Here is my small investigation. Everything was tested with the same config as above:
Simple react app:
The production build is 146 kb.
Time for tests:
We import some of them, but use only one:
The size is still 146 kb as the function is pretty small. If we use all of them - the size is 150 kb.
Tree shaking is working.
Lets use one component:
The size is now 147 kb. With all components - 152 kb.
Tree shaking is working.
The same example as above with one component still gives us 147 kb.
Tree shaking is working.
Now the size with one component is 152 kb.
Tree shaking is not working (exports with *). Issue is here.
The size with one component - 147 kb, with both - 149 kb. Now, we take random library and import it in one of the components (lets take B):
We want to use only one component:
Unfortunately, now the size is 185 kb.
So, the tree shaking actually removes the component, but some of the imports are included in the build (even if they were not used). Same thing happens with combined / reexported / nested files.
Any ideas why this happens with only some of the components and how to solve this?
I’m commenting with one thing only:
There have been multiple requests to the core team to answer one question: is this problem being worked on? After a very long silence we got some team members … answer different questions, but not this one.
This issue and #4784 mean one thing: tree-shaking does not work in Webpack despite all the articles and guides that claim otherwise.
There have been multiple examples showing it doesn’t work. There’s even a repo testing tree shaking set up by one of the participants of the thread. The result?
99% of the thread is on topic. What is the status of this bug?
We just came across this issue today too.
If it helps anyone else, we found a Babel plugin that provides a useful workaround in the meantime: https://www.npmjs.com/package/babel-plugin-transform-imports
As per the readme, it’s completely configurable, and can transform imports like this:
into this:
Note that this seems like it will only work if the package’s folder structure is consistent across named exports.
@jhabdas Your entire post is incorrect.
Dynamic
import
is not a WHATWG proposal, it is a proposal for TC39 (AKA ECMAScript, AKA JavaScript). Therefore it applies to all JavaScript targets, not just the browser.And the proposal for ES6 modules in Node specifically mentions that dynamic
import
will be supported (and in fact it’s the only way to load an ES6 module from inside of a CommonJS module).Also, dead code elimination does help with servers. Having larger JavaScript code means that Node takes longer to parse and execute the code. If you’re writing a microservice which creates/destroys servers dynamically on-demand, startup time is quite important!
Also,
require
is so slow in Node that some developers even bundle their server code to avoid the cost ofrequire
Dead code elimination has nothing to do with “being asynchronous”, it has to do with removing useless code, which means smaller file sizes, faster parsing, and faster execution. That’s useful in any situation.
Most compiled languages (e.g. C, C++, Java, Haxe, OCaml, Haskell, etc.) do dead code elimination by default, even though they’re used on the server. It’s a basic language feature, not a browser feature.
Dead code elimination cannot be a runtime feature in JavaScript, because when you use
import { foo } from "./foo.js"
the browser has to download the entirefoo.js
file, and afterwards it can then apply the dead code elimination.But applying the dead code elimination after downloading
foo.js
is useless, because it’s already downloaded the full file!The benefit of dead code elimination is that the browser doesn’t have to download the full
foo.js
file, it only needs to download the parts that it needs. So dead code elimination cannot be done by browsers, it must be done by tools (like Webpack, Rollup, Closure, etc.)If you want to “just build apps” then use a pre-existing template like
create-react-app
. Or just use plain old JavaScript. Nobody is forcing you to use these tools. The complexity isn’t going to go away, because some developers actually do need these features.A few random thoughts on annotations from me, since it’s cropped up in a few places (here and in related issue threads):
I certainly don’t view annotations as a complete or even good solution, and haven’t done any investigation into how they’d actually work in either Rollup or webpack. (Side-note, I really want to capitalise webpack but Sean yells at me when I do.) Long-term, the solution is
"module"
field in package.json files for this. Here’s a primer for anyone unfamiliarHi Appreciate all your hard work and my sincere thank you to who ever maintains Webpack. I’ve got a question , Do you guys have an idea of how far you’re away from releasing this fix or is it gonna be fixed at all or not ? We have a massive dependency on Webpack in all of our projects and looking at how big our bundles are is just horrifying, We might decide to move to rollup, but then we need to find a way to integrate rollup with Angular-Cli. Could you please give us a rough estimation on how long do you guys think we should wait possibly ? Much much appreciation again. Cheers
Right. I think these are the main issues here. Folks want Webpack to really do Tree Shaking, like Rollup does, and it doesn’t. As I understand, it’s a feature on the Webpack roadmap that has not yet been implemented.
I believe what’s blocking implementation of proper tree-shaking (relying only on ES6 imports/exports, before passing the bundle to UglifyJS) is the fact that detecting whether a module has side effects may be undecidable like the Halting Problem.
One practical solution would be to allow authors to annotate their definitions as “tree shakable” (so-called “pure” definitions with no externally relevant side effects) or “non-tree shakeable” (so-called “impure” modules, that do have externally relevant side effects, e.g. d3-transition which mutates the prototype of another module). This would allow Webpack to bypass the complex process that Rollup implements; programmatically (and speculatively) deciding whether or not a module has side effects.
There are several threads related to this topic:
The syntax proposed by @sokra and implemented in UglifyJS is the following:
There’s some debate as to whether the annotations should go on the exports or the imports.
But conceptually, Webpack could leverage these annotations when walking the ES6 imports/exports tree before passing the bundle into UglifyJS, in order to achieve tree-shaking at the module level. This would be opt-in by library authors (e.g. D3), but at least it would take us closer to tree-shaking-nirvana than we are today.
@danbucholtz Tree shaking is the same thing as dead code elimination, because according to the ECMAScript spec modules must be evaluated even if their exports aren’t used.
Therefore if you use
import { foo } from "./foo"
Rollup will include the"foo.js"
module even if you never use thefoo
variable. Rollup uses dead code elimination to remove code which isn’t used.However, I agree with you that it should be handled properly by Webpack rather than relying upon another tool like UglifyJS.
Rather than Webpack duplicating Rollup’s efforts, I think it would be cool if there was a JS tool which could take ES6 modules, run tree shaking/dead code elimination on them, and then return tree shaken ES6 modules. That tool could be used by both Rollup and Webpack.
Tree shaking still not working as expected with rc.3
@trainiac yup, the problem has been documented here a couple of times already. This issue is now being geared more towards solving it. As they’ve said they’re going to be releasing this in the 2.4 feature milestone.
I know you’re eager to see this fixed, as it’s the reason many teams (including mine) upgraded to Webpack 2.0. We feel your pain.
Rest assured, it’s being worked on (and the core team probably wants it fixed more than the users do).
Exactly. Most libraries still ship with CommonJS that’s why it will take some time until tree-shaking will be actual effective.
Webpack will destroy itself without Tree Shaking. Most of the people who are using it don’t realise there are lots of unused code and big modules in their bundle, but as soon as they start working on optimisation, which every one knows it’s always 2 days before releasing your application 😃 , they realise shit has hit the fan terribly 😃
Looking at frameworks like Angular4, one of their selling point is “Tree Shaking” because the bundle is huge, but they never say it’s broken 😃
They should start using something else in Angular CLI
@xe4me up until a feature we are adding, webpack has always been treeshaking correctly based upon the esm module specification. But we are going to add an optin feature that package creators must implement. See https://github.com/webpack/webpack/tree/feature/pure-module/examples/pure-module
hi, all!
it’s working using bable/minify . here is the repo: babel-webpack-tree-shaking 😄
maybe we need someone to summarize the discussion. 😃
@arackaf I think that’s a great idea - to hint for the existence of side effects, rather than their non-existence, as this modules with side effects are less common than pure ones. Of the 31 modules exported from d3’s index.js, only one of them,
d3-transition
has side effects.@thelgevold That’s also a fantastic idea - to have import statements without “imported” symbols be the hint for side effects (for non-tree-shakeability). This would also work in the case of
d3-transition
, as that’s how it’s imported as well:import "d3-transition";
.These two ideas seem sound, non-intrusive, and implementable.
Indeed, but I hope this would be an opt-out, rather than opt-in workaround. In other words, I hope webpack wouldn’t compel authors to mark the 99% or so of their modules which do not cause side effects.
Ideally, I’d hope developers would be responsible for marking those modules which can NOT be tree shaken, no matter what. I know this might lead to some subtle bugs, as devs fail to do this and code is not run in production, but that could be managed in any number of ways: perhaps have developers opt into tree shaking with a new flag, and clearly list out which modules were shaken out, with a note about how to prevent this.
For those who follow this issue, or are subscribed to it, please note that it is now documented as “Caveats” in main documentation: https://webpack.js.org/guides/tree-shaking/#caveats
Let me address your sarcastic remarks.
Overall this sounds like a trolling off-topic.
I’ve been subscribed to this thread for a while. Seeing numerous attempts to find a suitable way of reporting a failing test case ranging from copy-pasting file output here in comments to putting up various sample repos on github. I thought that a standardized way of reporting such ES6 tree shaking bugs would be more appropriate. I’ve made up a utility repo for conveniently testing ES6 tree shaking and reporting bugs. https://github.com/halt-hammerzeit/es6-tree-shaking-test Bug reporters can fork that repo and add their own test cases posting links to their forks here for quicker response.
Also, I most definitely do not want to have to write things like
/*#__PURE__*/
in my code. For the most common cases, this should “just work”.I am also thinking that modules with side effects don’t need to actually export something.
Can’t we just import those modules using imports without “imported” symbols. Take an example from rxjs:
import 'rxjs/add/operator/catch';
Behind this import with no exported symbol we have the following module with side effect(s):
Then for “pure” modules without side effects we can just do:
import {MyToken} from './whatever'
Just through encouraging “pure” modules, we can probably reap most of the benefit of Tree Shaking through a convention where we limit side effects to small, isolated modules.
In case another minimal test case for tree shaking would be useful, here are two identical examples that create a custom bundle for D3.js, one with Rollup and one with Webpack:
One definition that exemplifies the difference is
Local
- this appears in the Webpack bundle, but not in the Rollup bundle. It is exported fromd3-selection
, but not imported anywhere, and has no side effects, so should not be included after tree shaking.Confirmed that the ES6 source (specified by
jsnext:main
andmodule
inpackage.json
) is being used by Webpack from the packagesd3-selection
andd3-transition
, not the CommonJS builds (search the bundle forconsole.log("This is a custom change to the ES6 source only for confirming that Webpack is using the ES6 module source, not the CommonJS build")
)Based on this experiment, I can confirm that WebPack (as of version 2.2.1) does not perform tree shaking as expected (at least for the case of these D3 modules).
For context, I ended up here while researching WebPack as an alternative to Rollup, following up on a conversation with @TheLarkInn and @Rich-Harris on potentially introducing annotations to flag ES6 modules as “pure” (free from side effects and thus tree-shakeable) from the D3 issue Rollup not tree-shaking from “d3” package #3076. Flagging exports as “pure” by adding hints in comments may be a potential path forward for making tree shaking work reliably. At least it would give library authors a way to easily make their libraries tree-shakeable.
dont use import as it bundles the code to one file
use import().then in all code. so it will be downloaded on demand.
you will see the bug here…
On Aug 10, 2017 6:36 AM, “Paul Sachs” notifications@github.com wrote:
Well this hurts me feels. I’ve been trying to get tree shaking working with webpack for many many many hours, At least now i know why it does nothing 💃
I’m always a fan of developers being responsible for knowing their tools, and how to use them, and being responsible when things break as a result of them not accomplishing this basic duty.
But, if we absolutely have to have safe defaults, I hope something like a new, opt-in flag could be added, telling Webpack everything is pure, and tree-shaking eligible. Or maybe some sort of new tree shaking config option where the developer opts into tree shaking by providing info on which, if any modules are impure, and therefore cannot be shaken out.
But what I hope we can all agree on is that manually specifying annotations for the potentially hundreds of modules which are eligible for shaking is not really feasible in practice.
mishoo/UglifyJS2#1261 appears to be solved by mishoo/UglifyJS2#1448. Where does that leave this issue?
I believe I’m having the same issue - tree shaking doesn’t seem to be doing much of anything. The project is https://github.com/redmountainmakers/kilntroller-ui. I haven’t narrowed it down to a specific bug but here is an example of a dependency I am definitely not using anywhere:
(image from the excellent [Webpack Visualizer](https://chrisbateman.github.io/webpack-visualizer/) tool)You could also use babel-plugin-lodash to automatically transform this
to something equivalent to
.mjs is concretely happening. Having talks about official statement.
@curran There is such a repo https://github.com/halt-hammerzeit/es6-tree-shaking-test You can Push your test cases there and update the table in the README. “simple” test case is tree-shakeable for both of them.
Tree shaking is a great optimization technique, don’t get me wrong.
However in the context of Angular, the importance of Tree shaking is somewhat exaggerated. Mainly because a few patterns in Angular are making code less “shakable”.
In short it’s due to symbol registration in NgModules. Registering a symbol in NgModule arrays makes the code behind the symbol unshakable since Tree shakers can’t distinguish this from actually using the symbol to instantiate something in your application code.
This pattern will give you the same issue even if you use Rollup.
I have more info about some of these pitfalls in Angular here: http://www.syntaxsuccess.com/viewarticle/pitfalls-in-angular-bundling
Also, if you are interested, I have a comparison between the different bundlers here: http://www.syntaxsuccess.com/viewarticle/angular-production-builds
As you can see in my example, Rollup with Tree shaking is only 4k smaller than a regular Webpack 2 bundle. If you switch to Webpack 3, the difference is only 2k. The reduction is due to less overhead from function wrappers in Webpack 3.
Of course these results will vary from app to app, but this is from a medium size Angular application, so it’s at least an interesting datapoint.
Angular CLI is currently using Webpack, but who knows, they might switch to something different in the future.
In any event, we shouldn’t exaggerate the importance of Tree shaking in Angular. All the extra benefits added from Webpack in general (e.g. lazy loading) more than weigh up for relatively minor increase in bundle size.
I may link everyone to http://github.com/indutny/webpack-common-shake 😃
Angular experiments with
/*@__PURE__*/
: https://github.com/angular/tsickle/issues/452So, in what scenarios does tree-shaking currently actually work? Or does it…I’m confused.
No, webpack 2 should be able to do tree shaking with namespace imports, as long as the namespace export doesn’t escape.
The important part is that the imported file must use ES6 exports.
On Fri, 2 Sep 2016 at 0:47 jonathan schatz notifications@github.com wrote:
you could try it again with webpack beta 21, as I fixed an issue with reexports (
exports * from '...'
)https://github.com/sokra/react-rollup-webpack-test
Note that instead of adding the
sideEffects
flag to the config, it could also be added to the package.json ofreact-router-dom
.webpack 4 allows you to opt-in into more aggressive tree shaking.
It seems that if you have dead code that is removed via a DefinePlugin the dependencies won’t shake out. For instance:
I noticed that the code for DevOnly would still end up in the client bundle. I was able to get around this by using transform-define at the loader level instead of DefinePlugin at the plugin level. Here is an example of what this looks like:
@VladShcherbin I don’t think it’s fair to say that Tree shaking “doesn’t work” in general as an optimization technique. (If you are using Rollup).
However, you have to write your code a certain way to benefit from it. If you are using a framework, some of the code structures choices have been made for you. Unfortunately, in some cases these choices will impact the effectiveness of tree shaking.
Tree shaking is awesome, but it’s a much more “primitive” symbol discovery technique than people might think. There are well known limitations of this approach.
The most important limitation is that tree shaking happens at the statement level (e.g. class).
In the case of a class, Tree shaking will not “shake out” unused methods. Nor will it shake out modules imported by unused methods.
However, if you are aware of these limitations, you can definitely benefit from Tree shaking in Rollup by structuring your code accordingly.
You can of course give the Closure compiler a try if you need optimizations at a much more granular level.
@thelgevold tree shaking is a great idea, but it’s not working in Webpack and in Rollup. No matter how you write your es6/7/10 modules, it still won’t work in even simplest cases. But do you remember the hype it get, all the tutorials and other fairy tales about it?
In fact, if it was working, your bundle would be probably reduced by 2 or 3 times that you probably won’t even need lazy loading.
And here we now, a year later waiting for any member/maintainer to give us some info, is it going to work someday, is someone trying to make it better.
Exactly. Couldn’t have said it better myself thanks Tor
@xe4me: “They should start using something else in Angular CLI”… what would you suggest?
@cruzlauroiii The official tree-shaking guide has no info about
babili
. If using it solves the issue, has no side-effects and is the recommended way, it should be probably mentioned in the docs.It would be great to hear some info on this from the core team. @sokra @TheLarkInn
+1 that assuming named imports are side-effect-free is a really nice intuitive solution. If a named import should have side effects:
@thelgevold exactly my idea, too. I think this would be a good default convention: named exports/imports are pure, import the file for side effects like polyfills. If this would have an explicit opt-out for (some?) lib authors who want named exports/imports with side effects, I think this would be a good solution for now…?
@curran Webpack does not really do Tree Shaking. The issue is that Webpack can’t remove code at the statement level. Example: If you have two exports in your module, both exports will be included in your final bundle. (e.g. two classes) I have an experiment here where I show this: http://www.syntaxsuccess.com/viewarticle/tree-shaking-with-webpack
Instead of Tree Shaking, Webpack defers to Uglify for code removal. This means it will be able to remove stuff like unused variables etc, but it won’t be as effective as Rollup Tree Shaking.
there’s also this one (which despite the name supports generic usage):
https://github.com/lodash/babel-plugin-lodash
i’ve used it like this for
lodash
andreact-bootstrap
without any issues for quite a while now but i’m curious to try out thetransform-imports
plugin to compare.FYI: I created another minimal test case for tree shaking with webpack 2.
For me tree shaking works as long as the return value of an import is not used inside another function - even if this function is never imported itself. E.g. if you take this lib
logBaz
can be tree shaked, because it is just re-exported.logFizz
andlogBuzz
can be tree shaked, because they are a first level export. But: Sadlyfoo
ordoBar
can never be tree shaked even thoughlogFoo
andlogBar
are never imported.There we go, lot of lines like this one
WARN: Side effects in initialization of unused variable replaceLocation [0:24087,4]
These variables remain in the minified version.
Documentation is in progress. It’s still in alpha…
I’m experiencing a similar issue with
react-router-dom
. Modules such asMemoryRouter
andStaticRouter
shouldn’t be included in the bundle if using named exports. Tested on a freshcreate-react-app
installation withreact-router-dom
and the followingApp.js
:This is not a fix for #4784 but this may help someone… I’ve created the following gist that explains how to compile npm packages using babel; and ensure that the application using these npm packages can benefit from tree shaking. https://gist.github.com/ncochard/6cce17272a069fdb4ac92569d85508f4 This illustrates the use of webpack’s mainfields and babel’s babel-preset-env. Feedback welcomed!
So, I did some experiments over the last couple of days cause Tree Shaking didn’t seem to work on our Angular projects (using Angular CLI for the build chain).
Seems to me that Webpack / UglifyJS is only able to do tree shaking on the “first level” (not recursively). For instance, when importing dependencies into my
AppModule
(the root Angular module), tree shaking works just fine. However, when importing dependencies into some other Angular module which is then imported into theAppModule
, tree shaking fails - and everything (!) gets imported.Not sure if this is only related to Angular projects, or whether it’s an issue with the Angular CLI / Webpack / UglifyJS …
Yes let’s keep the thread on topic. However any usability concerns can go into a separate issue for discussion! 🤗
It would be interesting to have a kind of “Rosetta stone” of small code samples with patterns that are known to be tree-shakeable and not-tree-shakeable, via Rollup vs. Webpack. Does anyone know of any resource like this?
It works fine by balili with local module, but it is broken with module in node_modules like lodash-es
I did an experiment with Webpack and Babili . Since it works with ES6, the dead code removal results are better.
Exported classes are not included unless they are actually imported. http://www.syntaxsuccess.com/viewarticle/webpack-tree-shaking-with-babili
this is a working tree shaking in webpack babili
https://github.com/blacksonic/babel-webpack-tree-shaking
Yeah, this is the article that has started it all: http://2ality.com/2015/12/webpack-tree-shaking.html
Marking modules as unused along with uglify processing was named “tree shaking”. But it isn’t as was explained by rollup’s author @Rich-Harris https://medium.com/@Rich_Harris/tree-shaking-versus-dead-code-elimination-d3765df85c80 Or is it?
I think we need a Wikipedia article about tree shaking. https://en.wikipedia.org/wiki/Tree_shaking
@markdalgleish that’s a fantastic workaround, thanks for sharing!
I did a small investigation a while ago. Based on a couple of issue threads and HackerNews comments, I think that proper tree-shaking (rollup way) will be implemented after v2 is released. There is also an open issue in UglifyJS which is preventing good tree-shaking results right now. https://github.com/mishoo/UglifyJS2/issues/1261
Adding another datapoint: Tree shaking seems ineffective in real-world projects.
I have a vendor.js chunk with a bunch of libraries (React, ReactDOM, MobX, etc.). Using Babel 6.14.0 and Webpack 2.2.0.rc.3 the file compiles to 2.34MB without minification.
Enabling minification brings it down to 939kB.
Disabling
unused
anddead_code
flags on UglifyJsPlugin adds 30kB. This implies tree shaking was only able to remove 4% of the library code. That does not sound like a lot.Adding/removing the
{modules: false}
option on Babel does nothing. Potentially (probably) the libs are precompiled to use CommonJS.The initial large file size leads me to believe the libraries in question are not pre-minified.
Are my expectations of what tree shaking can do too high? Do we have to wait for libraries to stop distributing compiled versions?
@andreigabreanu with such a simple example it may work, but with daily projects that use another libraries it does not.
Here is my simple test repo, super simple example with React & React Router. React Router was chosen as its creators tried to make it compatible with rollup/webpack tree-shaking, but it still does not work with the latest versions.
i think webpack2 needs that to be:
This seems to be a problem only in combination with reexports…
Ran into the same issue with the latest rxjs version when importing like this:
import { concat, map, merge, share, switchMap, take, toArray } from 'rxjs/operators';
instead of separate imports 😦Yes @weisk, but if you use lodash excessively, this gets bothering pretty fast, especially since there is alternate ways like @noppa mentioned.
@bkniffler why would you need it for lodash? In lodash you can import the micromodules directly, I dont think you should import it globally
@darrenscerri Webpack doesn’t optimize named exports. While Uglify can remove unused modules sometimes, it’s not guaranteed to be removed.
For example, react-router-dom: https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/index.js
There’s no guarantee there’s no side effects when you import something. So Uglify will rather keep modules than remove them.
If there was some way module creator can tell that their module doesn’t have side effects. As it was already discussed in this thread, Uglify has some special comments specification of “pure” modules.
TL;DR: Use this:
@dominique-mueller Well importing in the Angular sense
NgModule
s into otherNgModule
s is quite distinct from importing with regular imports such asimport { Component} from '@angular/core';
.An entry file for Angular in Webpack is further typically not your
AppModule
. Should rather be amain.ts
or similar which imports theAppModule
and bootstraps it.So think you are more likely talking about an Angular issue. But need a more concrete example to be sure.
That being said the
angular-cli
team is going through some good strides to do tree shaking using their build optimizer which is integrated intoangular-cli
but also available as a separate webpack plugin. It’s still under active development.@TheLarkInn
Just want to make sure that
pure-module
will also solve those two cases.For example see: https://github.com/shahata/tree-shaking/blob/master/README.md
Things are happening:
https://github.com/webpack/webpack/tree/feature/pure-module/examples/pure-module
One tip. Delete your
.babelrc
file in your project dir. Mine had this:This caused the minimal example to not work at all. After this, the square function stripped off from the production bundle as it should.
However, in
three.js
still imported everything. Maybe related issues:https://github.com/mrdoob/three.js/issues/10711
https://github.com/rollup/rollup/issues/1130
https://github.com/mrdoob/three.js/issues/9852
Some info from core members would be great since lots of devs are interested in this.
I saw improvements in version 3.
Not mind blowing results though… Roughly a 2k reduction in bundle size, but also slightly faster execution times.
Write up: http://www.syntaxsuccess.com/viewarticle/webpack-3-vs-webpack-2
Updating to 3.0.0 and turning on scope hoisting reduced the file size by 1kb. According to feature votes and v3 release article it seems that this issue won’t be fixed anytime soon. 😕
That’s because of so called “index.js problem”. That’s why people say “tree shaking is broken”. It never did.
vs
if you see the definition of three shaking, shaking the tree so dried leaves fall off.
imports are removed if not used.
so imagine if done correctly, all the non used imports, function, methods or variables will not be included in the source code if not used in the main function executable.
ive tried uglify and babili.
only this project works with tree shaking and that is webpack babili combination
https://github.com/blacksonic/babel-webpack-tree-shaking
UnusedComp
wasn’t dropped because it was referenced by the staticUnusedComp.propTypes
outside the class IIFE which is considered a side effect from uglify’s perspective.I’m guessing that Babel generated
UnusedComp.propTypes
outside of the IIFE? If it were generated inside the class IIFE, then the unreferencedUnusedComp
would have been dropped.The code from the docs is an example of code that can be removed by uglify. If you use plain functions or variables, regular minifcation will act like a “tree shaker”.
However since we live in the age of Typescript, things get more complicated.
A Typescript class like:
Transpiles down to something like this:
Since this is a more complicated structure, Uglify will not strip it out during bundling. That is why you see all exported classes in your bundle.
I show this in my article here: http://www.syntaxsuccess.com/viewarticle/tree-shaking-with-webpack
I know this won’t work for third party code, but my recommendation is to define only one class per module. That way you get around the tree shaking limitation in Webpack for now.
Would it be possible to integrate some of the internals from Rollup to extract the tree of fragments that should be included in the bundle? Then piece the bundle together from that? Excluding anything that isn’t in the Rollup tree.
Shouldn’t that in theory be compatible with webpack bundling if you ran the “Rollup” step as part of a preprocessing step? I apologize if I am missing something obvious, but what would be the argument against this approach?
I would love to see proper Tree Shaking support instead of relying on “after the fact” code removal techniques via Uglify.
I read about the plans to integrate Closure Compiler as well. That would likely yield even better results, but you will likely face some external lib compatibility issues in ADVANCED_MODE. I guess it could be an opt-in for code bases where you control all the code and can push for Closure compatibility.
Thanks, Tor
I would hope any reliance on UglifyJS is removed and that Webpack would either handle it directly or as https://github.com/webpack/webpack/issues/2867#issuecomment-280695858 says, a tool independent of minification, especially so things like this can move forward: https://github.com/webpack/webpack/issues/3673