Concepts
To understand how tree shaking works, you need to know a few key concepts. These concepts relate to how JavaScript modules work and how they can be optimized.
1. Static vs Dynamic Imports
In JavaScript, there are two ways to import modules: static imports and dynamic imports. Static imports use the `import` statement and are fully declarative. They allow the bundler to analyze the dependencies at build time. Dynamic imports use the `import()` function and load modules at runtime. Because they happen at runtime, dynamic imports cannot be tree-shaken.
2. Named vs Default Exports
JavaScript modules can export values in two ways: named exports & default exports. Named exports allow you to export multiple values from a module, each with its own name. Default exports allow you to export a single value as the default for the module. Tree shaking works best with named exports because they allow for a more granular analysis of what's being used.
3. Live Code vs Dead Code
Live code is code that is actually used by your app at runtime. Dead code, on the other hand, is code that is never used. The goal of tree shaking is to identify & remove dead code so that only live code remains in your final bundle.
4. Side Effects
A side effect is any code that performs an action other than calculating a value. Examples include modifying a global variable, making an HTTP request, or logging to the console. Code with side effects cannot be safely removed by tree shaking even if its result is unused because removing it would change the program's behavior.
Imports
How you import modules in your JavaScript code has a big impact on tree shaking. As mentioned in the concepts section, static imports are better than dynamic imports for tree shaking.
With static imports, you use the `import` keyword at the top of your file to declare which modules you want to import. This allows the bundler to analyze the dependencies at build time.
For example :
import { myFunction } from './myModule';
In this example, we're importing the `myFunction` named export from the `myModule` module. If `myFunction` is the only thing we use from `myModule,` then the bundler can safely remove the rest of the code in `myModule` during tree shaking.
On the other hand, dynamic imports use the `import()` function to load modules at runtime.
For example :
import('./myModule').then((module) => {
module.myFunction();
});
In this case, the bundler cannot analyze the dependency at build time because it doesn't know which module will be loaded at runtime. As a result, it cannot tree shake the unused parts of `myModule.`
Note: To maximize tree shaking, use static imports wherever possible. Only use dynamic imports when you truly need the flexibility of loading modules at runtime.
Here is the "Side Effects" section:
Side Effects
Side effects are important to consider when it comes to tree shaking. A side effect is any code that performs an action other than just calculating a value. A different type of side effects are :
- Modifying a global variable
- Making an HTTP request
- Logging to the console
- Mutating an object passed as an argument
Tree shaking cannot safely remove Code with side effects, even if its result is unused. This is because removing the code would change the program's behavior.
For example:
let globalVar = 0;
function sideEffectFunction() {
globalVar = 1;
console.log('This function has side effects');
}
function pureFunction(x) {
return x * 2;
}
sideEffectFunction();
console.log(pureFunction(3)); // Unused result

You can also try this code with Online Javascript Compiler
Run Code
Output
This function has side effects
6
In this code, `sideEffectFunction` modifies the global variable `globalVar` & logs a message to the console. Even if the result of calling `sideEffectFunction` is never used, the bundler cannot remove this function because doing so would change the value of `globalVar` & prevent the message from being logged.
On the other hand, `pureFunction` is a pure function- it has no side effects, and its result depends only on its input. If the result of calling `pureFunction` is never used, the bundler can safely remove it.
Note: To allow for effective tree shaking, it's best to avoid side effects where possible and isolate them when necessary. Pure functions are easier for bundlers to analyze and optimize.
Mark a function call as side-effect-free
In some cases, you may have a function that the bundler thinks has side effects, but you know it actually doesn't. In these situations, you can mark the function call as side-effect-free to tell the bundler that it's safe to remove the call if the result is unused.
The way you mark a function call as side-effect-free depends on the bundler you're using. For webpack, you can use the `/*#__PURE__*/` comment before the function call.
For example :
function myPureFunction(x) {
return x * 2;
}
/*#__PURE__*/myPureFunction(3); // Unused result
In this code, the `/*#__PURE__*/` comment tells Webpack that `myPureFunction` is a pure function and that the call can be safely removed if the result is unused.
For Rollup, you can use the `treeshake.annotations` option in your configuration file. This option lets you specify which functions should be treated as pure.
For example :
// rollup.config.js
export default {
input: 'src/main.js',
output: {
file: 'bundle.js',
format: 'cjs'
},
treeshake: {
annotations: true
}
};
// src/main.js
/*@__PURE__*/myPureFunction(3); // Unused result
In this case, the `/*@__PURE__*/` comment in the code works with the `treeshake.annotations` option in the Rollup configuration to mark `myPureFunction` as pure.
Note: Marking functions as side-effect-free can help the bundler remove more dead code & create a smaller bundle. However, it's important to only do this for functions that you're sure have no side effects.
Minify the Output
After tree shaking has removed the dead code from your bundle, you can further reduce the bundle size by minifying the code. Minification is the process of removing unnecessary characters (like whitespace, comments, & long variable names) from your code without changing its functionality.
Minification works by:
1. Removing whitespace, comments, & unused code
2. Shortening variable & function names
3. Collapsing multiple statements into one
This is a simple example of code before & after minification:
// Before minification
function addNumbers(a, b) {
// Add two numbers
return a + b;
}
// After minification
function a(b,c){return b+c}
As you can see, the minified code is much shorter, but it still does the same thing as the original code.
Most bundlers have options for minifying your code as part of the build process. For example, in webpack you can use the `TerserPlugin` to minify your JavaScript. Let’s see what that might look like in your webpack config:
Javascript:
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimize: true,
minimizer: [new TerserPlugin()],
},
};
In Rollup, you can use the `terser` plugin to achieve the same thing:
Javascript
import { terser } from 'rollup-plugin-terser';
export default {
input: 'src/main.js',
output: {
file: 'bundle.js',
format: 'cjs'
},
plugins: [terser()]
};
By minifying your code after tree shaking, you can reduce your bundle size as much as possible, which leads to faster load times and better performance for your web app.
Frequently Asked Questions
Can tree shaking remove unused code from third-party libraries?
Yes, tree shaking can remove unused code from third-party libraries, but only if the library is written in a way that supports it (using ES6 modules & exporting functions/variables individually).
Does tree shaking work with CommonJS modules?
Tree shaking works best with ES6 modules, but some bundlers (like webpack) can perform a limited form of tree shaking with CommonJS modules using static analysis.
Can tree shaking cause problems if done incorrectly?
Yes, if you mark a function as side-effect-free but it actually has side effects, tree shaking might remove code that's necessary for your application to work correctly. It's important to be careful & only mark functions as side-effect-free when you're sure about it.
Conclusion
In this article, we've learned about tree shaking, a powerful technique for removing dead code from your JavaScript bundles. We've explained key concepts like static vs. dynamic imports, named vs. default exports, side effects, and minification. We've also seen how to use tools like Webpack and Rollup to implement tree shaking in your build process.
You can also check out our other blogs on Code360.