Tree Shaking with Webpack

- Developer and Blogger
Published: Mon Mar 06 2017

In this post I will take a look at how Webpack handles Tree Shaking.

Webpack is a very popular bundler used by many different JavaScript frameworks.

Webpack offers a ton of functionality, and is generally a great choice if you are looking for a bundler.

However, I do think there is room for improvement when it comes to Webpack bundle sizes. In general I see two areas where Webpack bundling could improve.

Extra Function Scopes

Webpack bundling adds an extra function scope around every bundled module.

This means we get some extra baggage in the form of function wrappers.

In the example below I am showing an example of one of these function wrappers.

/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return FriendService; }); /* unused harmony export CarService */ var FriendService = (function () { function FriendService() { } FriendService.prototype.getFriend = function () { return 'Joe Smith'; }; return FriendService; }()); var CarService = (function () { function CarService() { } CarService.prototype.getCar = function () { return 'BMW'; }; return CarService; }());

This is used internally by Webpack, but the downside is that it translates into extra overhead in the final bundle size.

By contrast Rollup does not add these extra wrappers.

Update: The overhead from the extra function wrappers is specific to Webpack 2. In Webpack 3 there are no more individual function wrappers if you are using the ModuleConcatenationPlugin. Check out one of my other articles for more details.

Tree Shaking at the statement level

Webpack is currently not able to Tree Shake at the statement level. This means you are forced to include entire modules. If your module exports multiple classes, your bundle will include all of them, even if you only use one of the classes.

Below is an example of this:

export class FriendService { getFriend() { return 'Joe Smith'; } } export class CarService { getCar() { return 'BMW'; } }

I have created a single ES6 module where I export two classes. In my app I am only importing FriendService.

import { FriendService } from './friend-service'; console.log('Hello World', new FriendService().getFriend());

My app does not need CarService to run, but Webpack includes it regardless. I have noticed that Webpack flags CarService as an unused export (/* unused harmony export CarService */), but it's still included in the bundle.

I have included the final bundle below. As you can tell, CarService is included.

(function(n){function t(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return n[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var e={};return t.m=n,t.c=e,t.i=function(n){return n},t.d=function(n,e,r){t.o(n,e)||Object.defineProperty(n,e,{configurable:!1,enumerable:!0,get:r})},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,t){return,t)},t.p="",t(t.s=1)})([function(n,t,e){"use strict";e.d(t,"a",function(){return r});var r=function(){function n(){}return n.prototype.getFriend=function(){return"Joe Smith"},n}();(function(){function n(){}return n.prototype.getCar=function(){return"BMW"},n})()},function(n,t,e){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=e(0);console.log("Hello World",(new r.a).getFriend())}]);

This is where Webpack and Rollup differ. Rollup is able to Tree Shake at the statement level.

An obvious workaround for this is to limit your modules to single exports. This is not unreasonable, but may not always be practical.

Technically I would argue that Webpack doesn't really do Tree Shaking. Since modules are included in their entirety, no code is really "shaken" out.

The code removal actually comes from including the Uglify plugin. This is more of an "after the fact" code removal approach. Unnecessary code is temporarily added to the bundle. Then Uglify goes and removes some of it. It will mainly be able to remove stuff like unused variables and plain functions though.

This approach has its limitation when dealing with transpiled classes. This is why Uglify can't remove code like my CarService class, leaving us with unnecessary code in the bundle.

The CarService ends up looking like this after transpilation:


The above structure will not be removed by minifiaction. However, Rollup would have removed it via its Tree Shaking implementation.

I still think Webpack is a great product, don't get me wrong. I've also seen that the Webpack community is very active, so I wouldn't be surprised if we see improvements in this area soon.

The code samples used for this post can be found here.