Tree Shaking - How to Clean up Your JavaScript
Tree shaking is a strategy web developers use to create leaner JavaScript bundles by getting rid of unused code. This guide will explain how to utilize tree shaking to your advantage with a module bundler such as webpack.
What is tree shaking?
Tree shaking, also known as live code inclusion, is a technique for optimizing code written in ECMAScript dialects like JavaScript. As apps accumulate dependencies over time, some of them are likely to fall out of use. The result is bloat, or messy code that wastes resources and drags down your application's performance. The aim of tree shaking is to remove unused JavaScript to ensure that only executable code gets sent to the user. If you think of your application as a tree trunk and its dead dependencies as dead limbs, the analogy is quite clear.
Targeting dead code used to be much more difficult in dynamic languages, but thanks to the static nature of ECMAScript 6 modules, it's now possible to prune dead dependencies through the strategic use of import and export statement. The concept was popularized by the bundler Rollup in 2015, but most other bundlers now support tree shaking. webpack has supported ES6 modules and tree shaking since version 2, but webpack 4 has some additional features that assist with dead code elimination.
Why perform tree shaking?
JavaScript is significantly more expensive to process than other web resources, such as images and HTML, because it must be parsed and compiled prior to execution. That's why web developers should strive to trim down their JavaScript code as much as possible before bundling it up. For example, the image below shows the execution time between 170 KB worth of JavaScript versus a JPEG image that is 170 KB in size.
Overall, tree shaking is more effective at improving performance than code splitting, which involves partitioning JavaScript into chunks and serving them strategically. Combining both techniques can result in substantial performance gains, but most of your payload savings will come from pruning your dependencies.
How does tree shaking work?
Tree shaking involves leveraging static import statements to include only the modules you need in your JavaScript bundle. When working in a developer build, everything within a module is automatically imported; however, in production builds, it's possible to tell webpack which modules you need. The resulting JavaScript bundle will be smaller and optimized for faster performance.
In modern JavaScript apps, dependencies are imported via static statements like in the example below:
import arrayMenu from "array-menu";
This code tells webpack to import everything from the module called array-menu; however, if your app only uses parts of a package, then there is no need to send everything to the user. When your app doesn't need everything in the module, then you can specify which dependencies you do need like so:
import { burger, fries, shake } from "array-menu";
Unfortunately, tree shaking is not a process that can currently be automated, so you'll have to proactively search for opportunities to implement it. Scour your app's main component file for static import statements and check all of your modules to determine if they contain any unused classes and functions. After that, go back and edit your component file to import only what your app actually needs.
Tree shaking: Before you begin
If you're using a JavaScript compiler like Babel, there is some preliminary work you must perform before you can start shaking your app. Certain presets, like babel-preset-env, automatically transpile ES6 modules to CommonJS modules. This process complicates tree shaking, so you should configure your preset to leave your ES6 modules as is. To accomplish this, just add the following code to your Babel configuration file:
{
"presets": [
["env", {
"modules": false
}]
]
}
Now your bundler should be able to analyze your dependency tree and help you pick out the dead code, but you'll need to run a minification process during bundling to take full advantage of tree shaking. To achieve this you can use the UglifyJS Plugin.
With this setup, webpack will detect unused code and mark it as such however UglifyJS will actually cleanup that code and eliminate it from the bundle.
An example of tree shaking in webpack
Let's look at a quick webpack example (courtesy of Alex Bachuk) to understand what tree shaking looks like in action.
Let's say we define the following modules.js
file:
export function drive(props) {
return props.gas
}
export function fly(props) {
return props.miles
}
Then we have the following index.js
file:
import { drive } from modules;
eventHandler = (event) => {
event.preventDefault()
drive({ gas: event.target.value })
}
In this example, fly()
isn't important and therefore won't be included in the bundle. Thanks to tree shaking it will be marked as dead code and cleaned up by UglifyJS.
webpack notes which classes are used and unused with the messages "harmony export (immutable)" and "unused harmony export" respectively. In order for UglifyJS to understand these comments by webpack, you'll need to ensure that optimization.usedExports
is turned on. The "unused" remains until it is removed during the minification process. If you're using Rollup as your JavaScript bundler, the unused code will be removed during bundling. Tree shaking only eliminated a few lines of code in this example, but when applied to larger projects, you can drastically reduce the size of your minified bundles.
More tree shaking tips
- If tree shaking doesn't seem to be having an effect on one of your libraries, check to make sure its methods are being exported using ES6 syntax rather than CommonJS. Certain plugins like webpack-common-shake purportedly support tree shaking for CommonJS modules, but you're better off sticking with ES6 modules.
- To set up tree shaking with Lodash, you must add babel-plugin-lodash to your Babel configuration in order for the regular import syntax to work.
- Side effects, such as when a function modifies an object outside of its scope, can complicate tree shaking. Consider using a plugin like eslint-plugin-tree-shaking to identify side-effects and prevent them from interfering with the process.
- Identifying the source of bloat can be more difficult in applications composed of many modules. Fortunately, tools like the webpack-bundle-analyzer can make tree shaking larger JavaScript applications a little easier.
- Don't be disappointed if tree shaking doesn't result in massive performance gains for your app. Tree shaking is just one way to reduce your JavaScript payload. Combine this method with others like code splitting and minification to make sure your web apps are running as efficiently as possible.
Summary
Different apps will obviously see different results, but setting up tree shaking is usually worth the effort either way. It's a good habit to get into as you build new JavaScript projects so that your apps starts off as lean as possible. Developers and users both benefit from trimmer JavaScript bundles.