Skip to main content

Tree shaking, an example with barrels

ยท 4 min read
Pere Pages
Tree shake
Tree shaking

Tree shaking is a term used in the context of web development, particularly with JavaScript and modern front-end build tools, to describe the process of removing unused code from your final bundle. It's like shaking a tree to let the dead leaves (unused code) fall off, so you end up with a lighter, more efficient bundle that only includes the code that's actually used in your application.

This concept became particularly relevant with the rise of module bundlers like Webpack, Rollup, and tools like Parcel, which analyze your import and export statements to determine which parts of a module are used by your application. If a function, class, or variable from a module is never used, the bundler can exclude it from the output bundle, reducing the size of your application and improving load times for your users.

Tree shaking works best with ES Modules (using import and export statements), as they are statically analyzable. This means the bundler can determine at build time which imports are used and which are not. CommonJS modules (using require and module.exports), on the other hand, are more dynamic and make it harder for bundlers to perform tree shaking efficiently.


To maximize the benefits of tree shakingโ€‹

  • Use ES Module syntax (import/export) over CommonJS (require/module.exports).
  • Ensure your dependencies are also published as modules that support tree shaking.
  • Make sure your tsconfig.json has the module field set to ESNext or ES6.
  • Minimize side effects in your modules, or explicitly indicate side-effect-free modules in your project's package.json using the "sideEffects": false property (or an array of files that do contain side effects).

Tree shaking is a powerful optimization technique that, when used correctly, can significantly reduce the size of your JavaScript bundles, leading to faster load times and a better user experience.


Example with barrelsโ€‹

If you're using a modern bundler like Webpack, Rollup, or Parcel, they employ techniques like "tree-shaking" to eliminate dead or unused code from the final bundle. This means that only the parts of the imported library that are actually used will be included in the final bundle, and unused exports will be dropped. However, tree-shaking is not 100% perfect and may not work as expected in some scenarios, such as with side effects or dynamic imports.

Barrels

In JavaScript, a "barrel" is a pattern used to simplify imports from a library or a module in your project. The idea is to consolidate or "barrel" exports from multiple files into a single, centralized file. This allows you to import various functionalities from a single place rather than having multiple import statements scattered across your code, pointing to different files.

For example, imagine you have a project structure with multiple utility functions, each in its own file within a utils folder:

/utils
- stringUtils.js
- numberUtils.js
- dateUtils.js

Without a barrel, if you want to use functions from these utilities in another part of your application, your import statements might look something like this:

import { capitalize } from './utils/stringUtils';
import { round } from './utils/numberUtils';
import { formatDate } from './utils/dateUtils';

To simplify this, you can create a barrel file (often named index.js or index.ts for TypeScript) inside the utils folder that re-exports all of these utilities:

// utils/index.js

export * from './stringUtils';
export * from './numberUtils';
export * from './dateUtils';

Now, you can import all your utilities through this single barrel file:

import { capitalize, round, formatDate } from './utils';

This approach not only makes imports cleaner but also helps with managing and organizing your codebase, especially as it grows in size and complexity. It's particularly popular in large-scale applications and libraries.

Here is a quick example using TypeScript:

Let's say the external library has the following:

external-library.ts
export const function1 = () => 'I am function 1';
export const function2 = () => 'I am function 2';
export const function3 = () => 'I am function 3';

You create an index for re-exporting the functions you need from the external library, like this:

export { function1, function2 } from './external-library';

In your main file, you only use function1:

main.ts
import { function1 } from './my-index';

console.log(function1());

In this case, modern bundlers with tree-shaking capabilities would include only function1 in the final bundle, and function2 will be eliminated since it's not being used.