Learn about tree shaking in Webpack 5

What is tree shaking?

Tree shaking is a technique used for removing the dead code from the application. It is applied for optimizing the code. Dead code is essentially code from the library which has been imported into the application unnecessarily along with the one being used. The term tree shaking was made popular by Rollup, a JavaScript module made by Rich Harris in the year 2015.

Tree shaking relies on ES2015 syntax - import and export.

Let’s say you imported a library containing 1,300 lines of code with a lot of exports and only want to use one of them. This would result in a lot of unused code being imported into the application and thereby increasing the bundle size.

How does tree shaking work?

Webpack 4 builds on the capability of built-in support for ES2015 modules as well as unused module export detection introduced in webpack 2 with a way to provide hints to the compiler via the sideEffects property in the package.json file which denotes which files in the application are pure and can safely be removed. The resulting JavaScript bundle will be smaller and optimized for faster performance when we tell webpack which modules we need in the production build.

For example, let’s say we import the following into our application -

  import Formik from "formik";

This tells webpack to import everything from the library. If our application only uses some of the parts from the package, then it’d unnecessarily import everything and send it to the user. To avoid doing so we can import it in the following way -

  import { Formik } from "formik";

Since webpack cannot automatically remove the dead code it makes use of minifiers like Terser to clean up the dead code i.e; eliminate the dead code from the bundle.

When we work in the developer build everything within a module is automatically imported however in the production build it is possible to tell webpack which modules you need.

Let’s see the tree shaking in action.

calculator.js

  export function add(a, b) {
    return a + b;
  }

  export function subtract(a, b) {
    return a - b;
  }

  export function multiply(a, b) {
    return a * b;
  }

  export function divide(a, b) {
    return a/b;
  }

index.js

  import { divide, multiply } from "calculator";

  console.log(divide(6, 2));
  console.log(multiply(5, 3));

In this example add() and subtract() are not being imported and therefore will be marked as dead code thanks to tree shaking.

Set the optimization to usedExports.

webpack.config.js

  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
  },
  devServer: {
    open: true,
    host: "localhost",
  },
  optimization: {
    usedExports: true
  },

And then set the webpack mode to development using webpack --mode=development in the console.

Now if we run npm run build and inspect the output bundle inside dist/main.js -

!*** ./src/calculator.js ***!
  \***************************/
      /***/ (
        __unused_webpack_module,
        __webpack_exports__,
        __webpack_require__
      ) => {
        eval(
          '/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   "divide": () => (/* binding */ divide),\n/* harmony export */   "multiply": () => (/* binding */ multiply)\n/* harmony export */ });\n/* unused harmony exports add, subtract */\nfunction add(a, b) {\n  return a + b;\n}\nfunction subtract(a, b) {\n  return a - b;\n}\nfunction multiply(a, b) {\n  return a * b;\n}\nfunction divide(a, b) {\n  return a / b;\n}\n\n//# sourceURL=webpack://my-webpack-project/./src/calculator.js?'
        );

        /***/
      },

See the line - /* unused harmony exports add, subtract */\nfunction add(a, b) {\n return a + b;\n}\nfunction subtract(a, b) which shows that the add and subtract functions are not being used and are marked as dead code.

What are sideEffects?

A side-effect is a code that performs some action when imported, not necessarily related to any export. A good example of a side effect is a polyfill. Polyfills typically don’t provide an export to be used in the main script, but rather add to the project as a whole. Side effects are hard to determine automatically. Bundlers like webpack take caution by assuming that every module potentially has sideEffects.

We need to tell webpack manually about the side effects by declaring it in the package.json file. If we don’t do it then webpack won’t be able to tree-shake anything away.

Very frequently your package won’t be using any side effects at all. In that situation, you can simply set sideEffects to false:

  {
    "name": "myPackage",
    "sideEffects": false
  }

If we have few files with side effects then we can list them:

  {
    "name": "myPackage",
    "sideEffects": ["dist/polyfill.js", "dist/calculator.js"]
  }

This makes the bundler assume that only the declared files/modules have side effects.

Sometimes we might only want to declare a certain number of function calls in a module/file sideEffect free instead of marking the whole file as sideEffect free. For example -

  function doSomething() {
    // some code...
  };

  doSomething();

Here bundler would be unable to tell if the function call contains any sideEffects. We can do this in two ways -

  • We add the module/file to the sideEffect in the package.json file.
  • We can mark the code as pure -
  function doSomething() {
    // some code...
  };

  /*#__PURE__*/ doSomething();

Pure functions are the ones that always return the same result if the same arguments are passed.

Summary

We looked at what is tree-shaking, and how tree-shaking works with examples. We also had a look at what are side effects, how we mark the code/package for side effects, and how to mark a single function call as a side effect instead of marking the whole file/module as side effect free.

Need help on your Ruby on Rails or React project?

Join Our Newsletter