Making lodash tree-shakable
Tags: typescript, javascript, webdev, programming
Published: 2023-11-19 19:23:30

**TL;DR**

```
1- Replace `lodash` with `lodash-es`
2- Ditch default imports
3- Prefer namespace imports - import * as _ from "lodash-es"
4- Setup a linter rule to not allow this happen again
5- See great bundle size improvements 😎  
```

## Lodash

Lodash is a JS utility library that delivers small functions capable of easing the way we work with arrays, numbers, objects, strings, etc. 

- `isEmpty`
- `lowerCase`
- `concat`
- `defer`
- ...

And although it's about 11 years old, `lodash` is still today, the most "depended-upon" package, according to the [npm registry](https://gist.github.com/anvaka/8e8fa57c7ee1350e3491). So it's very likely that you are using it directly or indirectly through one of your dependencies.

### CommonJS

When `lodash` was first introduced in 2011, `CommonJS` was the common standard to write JS modules, however, that standard was mainly intended to be used in server-side applications and never to reach the browser.

But in 2023, the `lodash` library is still being widely used in web applications, and of course, it's still written in the `CommonJS` format.

### Tree Shaking in CommonJS

> _Tree shaking is a process in modern JavaScript bundlers that eliminates unused code during the build process, resulting in smaller and more optimized bundles._

Because `CommonJS` was originally designed with server-side applications in mind, it didn't prioritize a crucial aspect for browser-based environments: **the ability to be tree shaken**.

This happens because CommonJS allows for highly dynamic imports/exports, that bundlers can't simply pick up and do a fully static analysis on, to understand if certain code should be kept or eliminated in the final bundle.

For more in-depth information on how `CommonJS` works when it comes to tree shaking, make sure to take a look at this great [article](https://web.dev/articles/commonjs-larger-bundles).

### A Tree-Shakable Alternative

In today's web development, `ECMAScript` is the go-to standard for writing JS modules and it's being widely adapted not only on the browser but also on `node` environments. 

This standard is much more mature and offers much better support for static analysis and tree shaking due to its more declarative and static nature.

## Tree-shaking `lodash`

> _Now that we have gone through why tree shaking doesn't play well with `lodash`, let's dive into what we can do we make it tree-shakable._

**1- Replacing `lodash` with `lodash-es`**

Due to the huge amount of benefits of using ESM (ECMAScript Modules), the [`lodash-es`](https://www.npmjs.com/package/lodash-es) lib was created and it's now the recommended package to be installed if you want to continue to use `lodash`, it is still the same library, but exported as ES modules.

**2- Stop using the default import**

Another very common practice that might completely ruin tree shaking, is doing a default import when using `lodash` or any other lib that exports everything from the default exported variable.

```tsx
import _ from "lodash-es";

// `_` contains every single lodash's function 
_.add(1,2);
_.isEmpy([]);
_.lowerCase("HEY");
```

In the example above, although we are only using 3 functions, we are actually importing the whole lib, because the `default` export is an object that contains all the lib's utilities.

**3- Use namespace imports**

Namespace imports are a way to import every single exported member from a module individually.

```tsx
import * as _ from "lodash-es";

// `_` contains every single lodash's function 
_.add(1,2);
_.isEmpy([]);
_.lowerCase("HEY");
```
The difference between this and the `default` import, is that we are importing `add`, `isEmpty` and `lowerCase` individually and as such, every other import that isn't used will be removed at build time.

Of course, you could also import each utility individually, but because of how similar these function names are to the ones provided by native JS, I highly recommend that you keep the `_` prefix when using `lodash`.

```tsx
import { some } from "lodash-es";

// makes it harder to understand which one is each ❌
some([null, 0, 'yes', false], Boolean);
[null, 0, 'yes', false].some(Boolean);

// ------------------------------------------------

import * as _ from "lodash-es";

// makes it very easy to understand which one is each ✅
_.some([null, 0, 'yes', false], Boolean);
[null, 0, 'yes', false].some(Boolean);
``` 
### Bonus tip

To safeguard against unintentional slip-ups, such as default imports that can bloat your app's bundle size, I strongly recommend incorporating an ESLint rule to NOT allow using the `default` import of `lodash-es`.

```js
// eslintrc.js
module.exports = {
    rules: {
        "no-restricted-syntax": [
            "error",
            {
                message:
                    "Do not import default from lodash-es. Use a namespace import (* as) instead.",
                selector:
                    'ImportDeclaration[source.value="lodash-es"] ImportDefaultSpecifier',
            },      
        ],
    },
};
``` 
**Summary**

1- Avoid using CommonJS libraries in browser-based applications
2- Be mindful when using default imports and prefer namespace imports
3- Avoid these common pitfalls with linter rules

**Conclusion**

This approach is not exclusive to `lodash`, and it can actually be applied to a variety of packages, particularly those that are still being exported as `CommonJS`.

---

_Make sure to follow me on [twitter](https://twitter.com/P_FFigueiredo) if you want to read about TypeScript best practices or just web development in general!_