Smaller bundles with Webpack and Babili

Recently I took an objective look at file sizes for the bundles generated for formBuilder, Formeo.js and mi18n. Each module had between 30-52kb of bloat added to them so I set out to reduce. For this write-up I’ll be focusing on mi18n since it’s the simplest of the 3 and the gains are more easily measured. It’s also fresh in my mind so should be easier to write about.

Mi18n was the one with 30kb of overhead but since the source code is only 3.4kb that meant the generated bundle was 10x the size of src… Something clearly needed to be done.

First was to upgrade Webpack 1  to Webpack 2. Webpack 2 has built-in tree-shaking so we no longer need optimize.DedupePlugin and module occurrence ordering is now default behavior so we can also remove optimize.OccurrenceOrderPlugin. Mi18n uses some ES2016 and ES2017 features so we’ll be transpiling with Babel. Previously our Babel config was set right with the loader like so:

loaders: [{
  test: /\.js$/,
  exclude: /node_modules/,
  loader: 'babel',
  query: {
    presets: ['es2015', 'stage-3'],
    plugins: ['transform-runtime']
 }
}]

We were loading 2 presets, es2015 and stage-3, and the transform-runtime plugin. Running webpack --json | webpack-bundle-size-analyzer we find that transform-runtime is massive, and loads a lot of unneeded and unused stuff. To fix this we’ll change this loader and it’s configuration so it looks like this:

{ test: /\.js$/, loader: 'babel-loader' }

Ah that’s better, we’ve upgrade to babel-loader and Babel’s configuration has been moved to our package.json but could also have been in a dedicated .babelrc file. Moving babel configuration to .babelrc or package.json has the benefit of a single configuration anytime the code is transpiled. We’ve also changed the presets to a single one, Babel’s babel-preset-env. This new preset allows us to define environments where the module is expected to work instead of manually defining the presets and has include and exclude options which enable fine-tuning for even smaller builds. Check out Dr. Alex Rauschmayer’s excellent write-up on babel-preset-env for more info. For the mi18n module our babel configuration looks like this:

{
 "presets": [
   [
     "env",
     {
       "loose": true,
       "modules": false
     }
   ]
 ],
 "plugins": [
    [
      "transform-regenerator",
      {
        "asyncGenerators": false,
        "generators": false
      }
    ]
  ]
}

Enter Babili

Babili is an ES6 aware minifier from Babel. This means we no longer need to transpile our code before Uglifiying it which reduces modules/dependies in our build process as well as reducing the final size of our bundle. Our updated webpack.config.js now looks like this:

https://gist.github.com/kevinchappell/c7eb67b9411d905073dc91a0e01d1bb2

Conclusion

In the quest for a smaller bundle I tried Google Closure Compiler, Uglify and combinations of the 2 but in the end Babili resulted in the smallest file size. How much smaller? Upgrading and refactoring Webpack to use Babili and babel-preset-env brought our 34kb bundle down to 3.6kb, only .2kb larger than src. Now that’s a filesize I can live with.

Leave a Reply