Making lodash tree-shakable

pffigueiredo

Pedro Figueiredo

Posted on November 19, 2023

Making lodash tree-shakable

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 😎  
Enter fullscreen mode Exit fullscreen mode

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. 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.

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 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.

import _ from "lodash-es";

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

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.

import * as _ from "lodash-es";

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

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.

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);
Enter fullscreen mode Exit fullscreen mode

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.

// 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',
            },      
        ],
    },
};
Enter fullscreen mode Exit fullscreen mode

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 if you want to read about TypeScript best practices or just web development in general!

πŸ’– πŸ’ͺ πŸ™… 🚩
pffigueiredo
Pedro Figueiredo

Posted on November 19, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related