In this post I will show how Babili can improve dead code removal in Webpack builds.

In a previous post I explained that Webpack does not do Rollup-style Tree shaking.

Instead, Webpack relies on minifiers like Uglify to remove unused code. As I discussed in my previous article, this approach has limitations.

In summary, the main issue is that Uglify is not able to remove transpiled classes as a result of the ES5 representation of ES6 classes.

The current UglifyJS version does not support ES6, so we are forced to transpile before minifying.

UglifyJS2 supports ES6, but as far as I can tell, the UglifyJSPlugin does not seem to be compatible with UglifyJS2 yet.

One alternative is to switch to Babili and the Webpack Babili plugin.

Let's take a look at a simple example:

main.js
import { FriendService } from './friend-service'; document.write('Hello World', new FriendService().getFriend());
friend-service.js
export class FriendService { getFriend() { return `Joe Smith`; } } export class CarService { getCar() { return `BMW`; } }

As you can tell, friend-service.js exports two classes; FriendService and CarService.

Only FriendService is needed by my application. However, as I showed in my previous post, Uglify will not be able to remove CarService if we transpile to ES5.

Let's see what happens if we don't transpile, but leave it as ES6, and use Babili.

(function(a){function b(d){if(c[d])return c[d].exports;var e=c[d]={i:d,l:!1,exports:{}};return a[d].call(e.exports,e,e.exports,b),e.l=!0,e.exports}var c={};return b.m=a,b.c=c,b.i=function(a){return a},b.d=function(a,c,d){b.o(a,c)||Object.defineProperty(a,c,{configurable:!1,enumerable:!0,get:d})},b.n=function(a){var c=a&&a.__esModule?function(){return a['default']}:function(){return a};return b.d(c,'a',c),c},b.o=function(a,b){return Object.prototype.hasOwnProperty.call(a,b)},b.p='',b(b.s=1)})([function(a,b){'use strict';b.a=class{getFriend(){return`Joe Smith`}}},function(a,b,c){'use strict';Object.defineProperty(b,'__esModule',{value:!0});var d=c(0);document.write('Hello World',new d.a().getFriend())}]);

As you can see in the new bundle, CarService is no longer included. (Just search for “BMW”).

Perhaps the biggest benefit of this is not actually “Tree shaking”. It's likely going to be more impactful that this allows us to produce minified ES6 bundles.

This benefit can be seen in TypeScript projects since transpiled ES5 code is usually more verbose than the ES6 counterpart. I discus this more in one of my other articles.

One consideration here is of course that you now need your supported browsers to support ES6. In cases where you support older browsers, you can just transpile your bundle down to ES5, and still keep the mentioned "dead code removal" benefits.

Thoughts on Tree shaking

My view is that Tree shaking is a brilliant approach, but I think the practical benefit is often exaggerated. Not because of how Tree shaking works, but because code is often not structured for ideal Tree shaking.

I talk about some Angular Tree shaking hurdles here.

More broadly I also want to point out that Tree shaking is only effective if your modules export multiple statements. Personally I don't usually structure my modules with multiple exports since I think it often violates single responsibility principles.

One exception to this though is third party libraries. It might be practical to publish libraries as combined bundles with multiple exports. You see this format in Angular these days with the flat esm bundle format.

The webpack config for this sample can be found here:

const BabiliPlugin = require("babili-webpack-plugin"); module.exports = { entry: './src/webpack-babili/main.js', output: { filename: 'dist/webpack-tree-shaken-babili-bundle.js' }, plugins: [ new BabiliPlugin({}, {comments: false}) ] }

You can also find the source code on Github