BattlefyBlogHistoryOpen menu
Close menuHistory

Tree shaking lodash with Vite

Ronald ChenJuly 18th 2022

Vite is the new kid in town and I predict will eventually replace Webpack. Ah yes, the death of webpack has been foretold around many dev huddles. But I really do believe Vite will succeed as it is far faster and modern. For example, Vite embraces ES Modules as a core feature, whereas Webpack still considers ES module output as experimental as of July 2022.

Given Vite uses ES modules, is tree shaking free? Essentially yes in most cases. But one still needs to be careful with npm packages as many of them are still CommonJS.

But first we need to understand the problem and why we even need tree shaking.

The problem

lodash is one of the most popular npm packages. It is a library that offers many small utilities. The typical usage of it looks something like this.

import _ from 'lodash'

const emails = _.map(users, 'email')

Even though we used a single _.map function, when we build our app with Vite the entirety of lodash will be included. This is because the top level export of lodash is a single CommonJS export. Tree shaking only works with ES modules.

Doing a quick test, the bundle size comes out to be about 87 KiB.

The lodash team knows about this problem and does offer a solution with single function exports.

import map from 'lodash/map'

const emails = map(users, 'email')

This is annoying to manage but does bring the bundle size down to 18 KiB.

But we can do better! We can use proper ES modules.

The solution lodash-es

lodash-es is an ES module build that is offered by the lodash team. They publish both CommonJS and ES module builds. So tree shaking should just work right? Unfortunately there are gotchas.

How not to tree shake with lodash-es

import _ from 'lodash-es' // DO NOT DO THIS

const emails = _.map(users, 'email')

Unfortunately, lodash-es still exports everything as a single object as the default ES module export, which means nothing is tree shakable. The bundle size remains at 87 KiB.

The fix is to use a different import syntax.

How to tree shake with lodash-es

import * as _ from 'lodash-es'

const emails = _.map(users, 'email')

The bundle size goes down to 17 KiB.

import * as _ (demo) is creating a module object and means "gather all the exports into an object with the identifier _".

This makes lodash-es tree shakable because Vite can trace the usage of the module object. The module object isn't extensible, I.E. new properties cannot be added to it. Therefore, it is possible for Vite to comprehensively track which functions were being used and tree shake everything else. This isn't possible with the default export nor CommonJS default export as those are plain old JavaScript object.

What about _.chain? Can we tree shake that?

Unfortunately if one uses _.chain with Vite in dev mode, everything seems to work. But that is a trap as everything will be tree shaken off when a build is made.

But there is a workaround! Build your own chain.

import * as ld, { wrapperLodash as _ } from 'lodash-es'

ld.mixin(_, {
  chain: ld.chain,
  map: ld.map
})
_.prototype.value = ld.value

const emails = _.chain(users)
  .map('email')
  .value()

Oh boy that is complicated and is annoying having to manually add which methods one wants, but it keeps the bundle size small at 17 KiB.

Do you want to keep those JavaScript bundles small? You're in luck, Battlefy is hiring.

2022

Powered by
BATTLEFY