Tuesday, September 5, 2023

Webpack Tree Shaking Experiments

Overview

Webpack is capable of tree shaking unused code in order to make an application's bundles sizes smaller and more optimized. However, it can only do so under the correct circumstances.

I am performing a series of experiments iterating on a simple JavaScript application. I will modify its configuration and code to determine which settings result in the minimal bundle sizes.

Setup

The source code used for all of the experiments can be found here.

Here is a list of the main software used:

  • NodeJS 18.17.1 & NPM 9.6.7 (included with Ubuntu Lunar Lobster)
  • NPM workspaces to separate out each experiment
  • BundleAnalyzerPlugin to visualize artifact results
  • HtmlWebpackPlugin to generate the index.html file used to import the bundled JavaScript application.


Initial Application

This application was created by using the Webpack getting started guide but adjusting it slightly as it is placed in the ./packages/initial-app/ directory as an NPM workspace.

There are three main modifications made to the getting started app to test various parts of code splitting and tree shaking:

  1. Using multiple functions from the lodash library (join, sortBy, cloneDeep)
  2. Importing a json file. This is common when using i18n and for statically accessed configuration data.
  3. Importing another js file within the app. The goal here is to attempt to have this be a portion of the application that can be chunked out and ideally lazy loaded.

Results

 After running `npm run build`, we get the following files in the dist dir:
-rw-rw-r-- 1 brian brian      107K Sep  5 19:37 app.js
-rw-rw-r-- 1 brian brian      336   Sep  5 16:34 app.js.LICENSE.txt
-rw-rw-r-- 1 brian brian      236   Sep  5 16:34 index.html

We really are only concerned with app.js and will ignore the others going forward.

The following diagram is the output from the BundleAnalyzerPlugin:


The lodash parsed size in the bundle is 68.48 KB.

Looking a the bundle analyzer output, we can see we receive a single js file with lodash.js, users.json, timer.js, and index.js all nested inside. This gives us our baseline that we can now make improvements on. 

 

Experiment 1 - Treeshake lodash

The objective of this experiment is to make some minor modifications to the app configuration that result in the same user visible screen, but much better file sizes. This is done by changing how lodash methods are imported.

 

Results

-rw-rw-r-- 1 brian brian  65K Sep  5 20:01 app.js
 
 
 The lodash parsed size in the bundle is 26.02 KB
 

Conclusion

This optimization trimmed about 42 KB out of the resulting app by allowing Webpack to find unused code in lodash and remove it. This is a 40% reduction in size!

 

Experiment 2 - Treeshake using lodash-es

The lodash-es package is a special build of lodash that packages it in the more recent ES module packaging. Let's see what effect, if any using this might have.


Results

-rw-rw-r-- 1 brian brian  61K Sep  5 20:27 app.js
 
The lodash parsed size in the bundle is 22.99 KB

Conclusion

There is a slight advantage (4.17 KB) to using the ES version. This isn't a huge savings, but still a 6.5% decrease in size from Experiment 2.


 

Experiment 3 - Replace use of lodash

Modern JavaScript is very capable compared to what it used to be when lodash was created. Many of the functions in lodash have modern day alternatives. So with this version of the experiment we remove lodash completely.


Results

 -rw-rw-r-- 1 brian brian  38K Sep  5 21:28 app.js

 

This example doesn't include lodash at all.

Conclusion

This is is by far the best solution with a total size of 37.02 KB, about 40% the size of experiment 3 and 65% smaller than the initial application.


Summary

Looking at the experiment results, significant bundle size reduction can be obtained via the following:

  1. Using a library designed to be tree shakeable or built using ES modules
  2. Properly referencing the library's exported features with the proper import statement

Recommendations

  1. Try to avoid using outdated 3rd party apps that have been replaced with modern alternatives.
    1. Don't use underscore, or moment
    2. Try to avoid using lodash. If you do, be sure to import correctly in order to allow tree shaking.
  2. Use Webpack's performance configuration to cap your bundle sizes: https://webpack.js.org/configuration/performance/#performancehints
  3. When adding any new 3rd party packages to your application, do a before and after comparison of the bundle analysis to see the impacts adding the library have. (Some CI tools have plugins to help with this)

 

 

No comments:

Post a Comment