Put your webpack bundle on a diet (Part 3)

October 27, 2017


So we’ve performed an initial analysis and explored the use of webpack -p to bring our bundle size down from 1.7MB to 640KB. Then, we learned how to reduce that an additional 80KB by supplying our own configuration. But what comes next?

Part three will tackle more detailed optimizations related to some common modules, like Moment.js, Babel, and Lodash.

In this article, we’ll cover how to:

  • Remove locales when utilizing Moment.js

  • Implement the Date-fns library as a slimmer alternative to Moment.js

  • Only transpile what you need to with babel-preset-env

  • Avoid code duplication with Lodash

Cease the Moment.js

Moment.js is a library that helps you parse, validate, manipulate, as well as display dates and times in JavaScript.

The library supports many locales by default. This is great—but because it’s available in many languages, its payload is rather big. Fortunately, we can fix that. We’ll start by using the webpack IgnorePlugin in your webpack.config.js file to remove any unwanted leftovers from your bundle.

If you want to strip out _all_ locales, you can do this by adding the following config:

In some cases, you may need a couple of locales, but not all of them. Regex can help us out here too. Here’s an example if we only want English and German:

You can also achieve the same results using the ContextReplacementPlugin. Let’s take the same example, but specify which variants of the German language we want.

With all variants, including standard German (de) and the Austrian dialect (de-at):

Without variants (only de):

The technique used above can be recycled for other dependencies and files that we want to keep out of our optimized bundle.

Here’s a look at how our optimized webpack bundle measures up after removing the Moment.js locales:

Webpack diet Pt.3 - Moment.js optimization table

You can find the actual commit on GitHub.

This is already an improvement, but the Moment.js module is still too heavy considering we only need one specific date format for our app. Many cases only use Moment.js for very simple date manipulation or formatting tasks. And since Moment.js does not support tree shaking yet, we need to implement another library: date-fns.

Replacing Moment.js with Date-fns

Date-fns describes itself as, ”the most comprehensive, yet simple and consistent toolset for manipulating JavaScript dates in a browser and Node.js.”

Date-fns is similar to Moment.js in that they have a lot of overlapping functionality. But while Moment.js exposes one big object that can handle everything, date-fns is built for a more functional approach. Additionally, date-fns supports tree shaking when used together with babel-plugin-date-fns, a Babel plugin that replaces generic date-fns imports with specific ones.

As you can see below, utilizing date-fns in conjunction with this plugin will help trim down your bundle size and speed up your build time:

Webpack diet pt3 - table of bundle optimization with date-fns

Check out the commit for this improvement here.

Only transpile what you need to with babel-preset-env

Now that we’ve chosen a lean toolset for configuring dates, we can find other areas of our bundle to reduce. For instance, in its unoptimized state, our example app uses babel-preset-es2015 which was recently deprecated. This means that we must use another solution—the babel-preset-env package.

The babel-preset-env package is a Babel preset that compiles ES2015+, used in our unoptimized app, down to ES5 by “automatically determining the plugins and polyfills you need based on your targeted browser or runtime environments.”

The configuration for the plugin should be in the .babelrc file and look something like:

Something to note is the targets.browsers property. This is where you can set criteria for the included Babel plugins. Each plugin from the latest preset can be included if necessary, such as es2015, es2016, and es2017.

To get a preview of what browsers your configuration includes, you can use browserl.ist. Pass your browser criteria through as a list separated by commas, and the listed browsers will be included in the config file. You can find a query syntax spec on the browserlist repository.

What you get with babel-preset-env alone:

See this commit on GitHub.

So the optimization efforts above didn’t help... but why?

It’s because there is a common misconception that babel-preset-env v1 excludes polyfills. But in reality, your import of babel-polyfill is not touched at all in version one. The upcoming version two, however, will finally be able to exclude polyfills. To make this work, we have to upgrade to Babel v7.

First, run these commands:

Then enable the useBuiltIns flag in the .babelrc file:

Hint: Do not include babel-polyfill via an webpack entry. Instead, you should have it as an import statement at the beginning of the entry code file of your app.

Let’s take a look at our bundle size now:

Webpack diet pt3 - bundle optimization table with babel-preset-env

Find the actual commit for this improvement here.

We’ve almost reached the 100KB mark for our gzipped size, but we are not done yet—we can still squeeze more out of the bundle.

Avoiding code duplication with Lodash

Lodash is a JavaScript utility library that claims to deliver modularity and performance. It is currently available as lodash, lodash-es, lodash-amd, and about 286 other module variants, which only contain one method of Lodash.

Your dependencies might also depend on other versions of Lodash. This can result in a lot of code duplication because any of these dependencies might use different export of Lodash. To prevent this, we can take the following steps:

Step 1: Transform generic Lodash requires to cherry-picked ones

This can be achieved using babel-plugin-lodash and can help to decrease the bundle size.

The following should be added to your .babelrc file:

Step 2: Remove unnecessary lodash functionality

We can do this using the lodash-webpack-plugin which works great in combination with babel-plugin-lodash.

Our contentful.js SDK currently requires these Lodash features:

Additionally, our contentful-management.js SDK needs the following:

Just keep in mind that your other dependencies still might need some of these features.

Step 3: Avoid Lodash variant duplication

First, identify all the lodash variants in your production dependencies:

Now create webpack resolve aliases for every package in your dependency tree. To do this, alias them to the cherry-picked version of the basic Lodash package.

Here is an example of what your webpack config can look like:

And let’s check our bundle:

Webpack diet pt3 - bundle optimization table with lodash

Check out this commit on GitHub.

The pure minified file is now below 300KB and when gzipped, it will send less than 100KB over the net. That’s pretty impressive—but there's always room for improvement. If anyone has any further tricks, [start a discussion on our community forum.

What's Next

We've made significant improvements since our original 1.7MB bundle size. We could stop here, but what fun would that be?

In the next and last article of this series, we're aiming to hit webpack's recommended 250KB parsed size. We'll accomplish this by introducing chunk-splitting to enable better cacheability of your app over time and to only send relevant code to the user. Check back on the Contentful Blog next week or follow us on Twitter to stay updated.

About the author

Don't miss the latest

Get updates in your inbox
Discover new insights from the Contentful developer community each month.
add-circle arrow-right remove style-two-pin-marker subtract-circle