bene : studio is a global consultancy, helping startups, enterprises and HealthTech companies to have better product
Building a Tree Shaking friendly JavaScript Package
At bene : studio we love knowledge sharing to help the community of professionals. With 10+ years and over 100 projects behind us, we have a vast amount of experience. This is why we have launched a knowledge base on our blog with regular updates of tutorials, best practices, and open source solutions.
These materials come from our internal workshops with our team of developers and engineers.
Pardon the interruption, we have an important message!
We are looking to expand our team with talented developers. Check out our open positions and apply.
Why You Should Be Interested?
One of the ultimate goals for any frontend developer is to reduce the size of the final bundles shipped to the browser, but this could be tricky with all of the packages we are using. So let’s take a look at it by using an example.
Motivation
First, create a new CRA project.
npx create-react-app react-app-example
cd react-app-example
To analyze the bundle size, we need to install “source-map-explorer”.
npm i source-map-explorer
And update the scripts section of “package.json” file to have “analyze” script.
"scripts": {
+ "analyze": "source-map-explorer 'build/static/js/*.js' --gzip",
Now, let’s start by analyzing the initial bundle size of the empty app without any modification.
npm run build
npm run analyze
You can notice that the initial bundle size of the vendor chunk is 40.79 KB. Next, we need to install “lodash” and “lodash-es”.
npm i lodash lodash-es
Then update “App.js” just by importing “isArray” and use it without modifying anything else.
...
import { isArray } from "lodash";
function App() {
console.log(isArray([1, 2, 3]));
...
Build and analyze your app again.
Boom! Now it’s 65.07 KB with 23.83 KB (35.7%) only for lodash into our final bundle using “isArray” functionality. Let’s try again but with “lodash-es” package instead of “lodash”, so the only change would be:
import { isArray } from "lodash-es";
As it is expected from the beginning it’s only 46B for only using “isArray”. But why? We only imported ”isArray” from “lodash” and not the whole library, and why did it work as expected with “lodash-es”? I think we are ready to talk about tree shaking.
Tree Shaking
Tree shaking is a dead-code (unused code) elimination process. It relies on the static structure of ES modules (import and export). So CommonJS modules (required) can’t be shaken off directly because of their dynamic nature. So the bundlers we are using, like Webpack and Rollup, automatically tree shake our code for us.
In our first example, we used “import”. So why didn’t it work as expected? Ok, you did your part of the equation, but what about “lodash” itself? Was it tree-shakable enough?
Let’s do our experiment to build a simple library that is tree-shaking compatible.
mkdir library-example
cd library-example
npm init -y
And to demonstrate this example we need to install “lodash” here again.
npm i lodash
Create two files “A.js” and “B.js”, in “A.js”.
export default () => console.log("A");
And in “B.js”.
import { isArray } from "lodash";
export default () => console.log("B");
Note that we didn’t use “isArray”, we just imported it. Now create an “index.js” file to export both of these functions.
export { default as A } from "./A";
export { default as B } from "./B";
We are now ready to test our library with our react app. Create a new CRA app or use the one from the previous section and install it in your library locally.
npm i "{dir-to-your-library}/library-example"
In “App.js”, import function “A” from your library and call it:
...
import { A } from "library-example";
function App() {
A();
...
Now, it’s the moment of truth. Build your app and analyze it.
npm run build
npm run analyze
We have the whole “lodash” package (23.83 KB, +35%). Why? We didn’t import “B” which uses lodash. We just imported “A”, and as our library uses ES modules, we still have lodash in our final bundle.
CRA uses Webpack for bundling. Webpack needs a little help to determine if this part of the code is safe to be eliminated if it’s not used. To achieve this, you need to mark your library as side-effect-free in your library’s “package.json”. Add:
"sideEffects": false
Now build your app and analyze it again. Voilà, no more unused lodash in our final bundle.
Webpack sideEffects
So what is “sideEffects” and why does Webpack need it? Think of cases like polyfills, these packages bind themselves to the global object (window object for front-end) to be accessible anywhere. So you have to do nothing more than just importing them and then the magic happens.
import "core-js/stable";
Should Webpack eliminate them due to not being used directly? This can’t be decided automatically. So Webpack needs your help with this decision, and so that polyfills considers any side effects.
Tree-shakable packages
So far we mentioned “lodash” and “lodash-es” as examples. But you need to be careful with any package that you’re using in your front-end project. One of the most helpful tools that can help you here is bundlephobia. It helps you to find the cost of adding an npm package to your bundle. And with its new feature “Exports Analysis”, it helps to know the sizes of individual exports. It also shows possible alternatives for different packages.
Conclusion
To build a tree shaking friendly package you need to:
- Use ES modules. Make sure the final output of your package is using ES modules, not CommonJS or AMD.
- Config side effects properly to help bundlers like Webpack tree-shaking your package correctly.
Want to ask some questions? Send them to partner@benestudio.co and we are happy to set up a talk with our engineers.
Looking for a partner for your next project? Check out our services page to see what we do and let’s set up a free consultation.