John Stewart

Hi! I'm John. Software engineer, blogger, tech head, typically making or talking about software. JavaScript mainly. Android enthusiast.

Webpack tree-shaking

shake it off

In this post we are going to cover a couple different things:

  1. What is tree-shaking?
  2. How to use tree-shaking?
  3. How does webpack handle tree-shaking?

What is tree-shaking?

Tree-shaking is the process of analyzing your code and only including the parts you explicitly import for your application to run.

Okay, what is dead code elimination? Good question.

Dead code elimination approaches the problem from the opposite end. It analyzes your code after its been bundled and removes the code that is not being used in your application.

Ultimately they both are being used to remove unnecessary code.

That being said, here is a one liner to help explain it to all your friends.

Rather than excluding dead code, we’re including live code. (Tree-shaking)

-Rich Harris creator of RollUp

For more information on the differences between these two I recommend checking out this medium article by Rich. He does a much better job than I do explaining the differences between these approaches.

How to use tree-shaking?

Thanks to ES6 imports and exports we can explicitly say what pieces of code we want to import into our application.


The following code requires that you have webpack 2 installed plus babel-loader and babel-preset-es2015.

webpack 2

$ npm install webpack --save-dev

babel-loader & babel-preset-es2015

$ npm install babel-loader babel-core babel-preset-es2015 --save-dev

To use local version of webpack you need to add two scripts to your package.json. One for for dev(start) and one for prod(build). The -p flag in the build script tells webpack to run UglifyJs plus other production considerations when bundling. For more info, check out the building for production guide on


  "name": "",
  "version": "1.0.0",
  "description": "",
  "main": "",
  "scripts": {
    "start": "webpack --config webpack.config.js",
    "build": "webpack -p --config webpack.config.js"
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.22.1",
    "babel-loader": "^6.2.10",
    "babel-preset-es2015": "^6.22.0",
    "webpack": "^2.2.1"

Next we need to setup our webpack config. Should look something like this.


const path = require("path");

module.exports = {
  entry: path.resolve(__dirname, "src/index.js"),
  output: {
    filename: "index.bundle.js",
    path: path.resolve(__dirname, "build")
  module: {
    rules: [
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
            loader: "babel-loader",
            options: {
              presets: [
                ["es2015", { modules: false }] // IMPORTANT

The most important part in this is the option that we pass to the es2015 preset of { "modules": false }. This tells babel not to compile our ES6 modules to CommonJs modules.

We don't want babel to compile them to CommonJs because webpack 2 understand ES6 imports/exports which is what allows it to do tree-shaking.

Now with our basic setup in place lets take a look at some code.


import { foo } from "./utils";


Here we are stating that we would only like to import foo from our utils file and then we are calling foo.


export function foo() {
  console.log("foo called");

export function bar() {
  console.log("bar called");

Above we have a simple utils file where we are exporting two functions. Clearly we can see that we are not using the bar function. Let's see what happens when we run our build.

$ npm start

This will generate a file called index.bundle.js in your /build directory. If you open that up and scroll to what looks like your code you will notice the bar function is still there.


/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
  "use strict";
  /* harmony export (immutable) */ __webpack_exports__["a"] = foo;
  /* unused harmony export bar */
  function foo() {
    console.log("foo called");

  function bar() {
    console.log("bar called");

  /* 1 */
  /***/ function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
    /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__utils__ = __webpack_require__(

      __WEBPACK_IMPORTED_MODULE_0__utils__["a" /* foo */]


Whats important to note here is this bit.

/* unused harmony export bar */.

This basically tells UglifyJs that it can remove bar() from the code when it runs its check for dead code.

Now run the build script to optimize and

$ npm start

You should see less code now in your index.bundle.js.


  function(e, t, n) {
    "use strict";

    function r() {
      console.log("foo called");
    // NO MORE BAR :)
    t.a = r;
  function(e, t, n) {
    "use strict";
    Object.defineProperty(t, "__esModule", {
      value: !0
    var r = n(0);

How does webpack handle tree-shaking?

We already discussed this briefly above but its worth repeating.

In webpack 2 it understands ES6 imports/exports without the need for babel to compile those to CommonJs. Since webpack has this capability now, that means it understand which functions are used and which ones are not. So as webpack is bundling your code it marks pieces of code that it knows are not being used with certain flags.

These flags are then picked up by UglifyJs and its dead code elimination algorithm that results in a bundle that has been minified and removed of any excess code.

It seems that some consider this approach to be an issue. If you have strong feelings about how this should be handled then head on over there and voice your opinion.

If you liked this article and want to say hi, then you can find me on Twitter.