Jeremy Bernier

Upgrading to Webpack 2, and Tree-Shaking Results

March 15, 2017

Recently made the upgrade to Webpack 2 because I wanted the tree shaking functionality. Took about a day. Here are some tips, along with the results.

What is Tree-Shaking?

Tree-Shaking (popularized by Rollup) is the elimination of unused exports.

Unfortunately Webpack 2’s Tree Shaking doesn’t seem to eliminate unused exports inside packages — at least in my project (eg. all of Lodash’s functions are included inside my bundle including functions I don’t use).

Upgrading to Webpack 2

Webpack’s official migration guide is fairly comprehensive so I won’t waste time regurgitating what’s already there.

In your .babelrc’s es2015 preset, disable modules

Before:

"presets": ["es2015", "react"],

After:

"presets": [
  ["es2015", {"loose": true, "modules": false}],
  "react"
]

This will reduce the file size of your outputted JS.

Ensure that you’re not including the “react-hot-loader/babel” Babel plugin in your .babelrc file!

Tree-shaking won’t work if this plugin is added (wasted a lot of time on this).

Disabling .babelrc and importing your own config in Webpack (OPTIONAL)

Modify the “babel-loader” section in your Webpack config file to look like this (where .babelrc.js is a JS file containing your .babelrc config)

module: {
  rules: [
    {
      test: /\.js$/,
      use: {
        loader: 'babel-loader',
        options: Object.assign({ babelrc: false }, require('./babelrc.js'))
      },
      exclude: /node_modules/,
    },
  ]
}

Ignore the Warning

Now you will get a warning on every Webpack compilation that looks like this:

(node:35513) DeprecationWarning: loaderUtils.parseQuery() received a non-string value which can be problematic, see https://github.com/webpack/loader-utils/issues/56 parseQuery() will be replaced with getOptions() in the next major version of loader-utils.

Just ignore it, it’s a problem with one of your Webpack loaders and has nothing to do with you (seems that Webpack will be disabling this warning in a future update).

Results

Webpack 1.13.2

Version: webpack 1.13.2
Time: 25305ms
                                        Asset       Size  Chunks             Chunk Names
     js/contact_e752e5854d7a6f7422c9.chunk.js    1.43 kB       4  [emitted]  contact
                          chunk-manifest.json  274 bytes          [emitted]
     js/catalog_d3fe90090a995c7254ef.chunk.js    41.9 kB       1  [emitted]  catalog
         js/how_63fabed843b4144effd8.chunk.js    1.35 kB       2  [emitted]  how
     js/finance_7ae1304d69a4e204bd20.chunk.js    1.56 kB       3  [emitted]  finance
     js/vendor_876e13b789b38023cf83.bundle.js     394 kB       0  [emitted]  vendor
       js/about_467405304c50e28a9c63.chunk.js    1.44 kB       5  [emitted]  about
       js/main_d6bdb9cbe29bc1a164e5.bundle.js     342 kB       6  [emitted]  main
css/main_19b822f00587cc2e781c3bbe6bc852f3.css     205 kB       6  [emitted]  main
                                manifest.json  467 bytes          [emitted]
                                sw-toolbox.js    17.4 kB          [emitted]
   [0] multi vendor 136 bytes {0} [built]
    + 851 hidden modules
Child extract-text-webpack-plugin:
        + 2 hidden modules

Webpack 2.2.1

Version: webpack 2.2.1
Time: 22533ms
                                        Asset       Size  Chunks                    Chunk Names
       js/about_489704d5cc668862b9be.chunk.js    40.1 kB       0  [emitted]         about
       js/main_ba108242d5c525cd6d9d.bundle.js     325 kB       1  [emitted]  [big]  main
                          chunk-manifest.json   90 bytes          [emitted]
     js/vendor_a9ebdde44b1b55d21147.bundle.js     396 kB       2  [emitted]  [big]  vendor
css/main_70a9bee7273fab9ef275864d6bda5aca.css     205 kB       1  [emitted]         main
                                manifest.json  235 bytes          [emitted]
                                sw-toolbox.js    17.4 kB          [emitted]
   [1] ./~/react/react.js 56 bytes {2} [built]
  [18] ./~/react-redux/lib/index.js 475 bytes {2} [built]
  [42] ./~/lodash/lodash.js 527 kB {2} [built]
  [61] ./~/react-router/lib/index.js 3.62 kB {2} [built]
  [72] ./~/react-helmet/lib/Helmet.js 23.4 kB {2} [built]
  [89] ./src/modules/analytics/AnalyticsLib.js 1.54 kB {1} [built]
 [102] ./~/redux/es/index.js 1.08 kB {2} [built]
 [103] ./~/react-dom/index.js 63 bytes {2} [built]
 [134] ./~/react-router-scroll/lib/index.js 512 bytes {2} [built]
 [206] ./src/client/Device.js 856 bytes {1} [built]
 [207] ./~/history/lib/createBrowserHistory.js 3.38 kB {2} [built]
 [208] ./~/redux-thunk/lib/index.js 529 bytes {2} [built]
 [320] ./src/optimizely/client/index.js 464 bytes {1} [built]
 [840] ./src/client.js 3.36 kB {1} [built]
 [841] multi babel-polyfill lodash react react-dom react-helmet redux redux-thunk react-redux react-router react-router-scroll 136 bytes {2} [built]
    + 853 hidden modules
Child extract-text-webpack-plugin:
       [0] ./~/css-loader/lib/css-base.js 2.19 kB {0} [built]
       [1] ./~/base64-js/index.js 3.48 kB {0} [built]
       [2] ./~/buffer/index.js 48.6 kB {0} [built]
       [3] ./~/ieee754/index.js 2.05 kB {0} [built]
       [4] ./~/isarray/index.js 132 bytes {0} [built]
       [5] (webpack)/buildin/global.js 509 bytes {0} [built]
       [6] ./~/css-loader!./~/postcss-loader!./~/sass-loader/lib/loader.js!./src/style/index.scss 205 kB {0} [built]

main.js went from 342 KB to 325 KB, a 5% decrease in file size. Not exactly drastic, but I’ll take it.

Note that about.js went from 1.44 kB to 40.1 kB and the number of chunks were reduced. This is because apparently the minChunkSizePlugin which had minChunkSize=50000 (50kb) wasn’t working before for whatever reason. The tiny <2kb chunks were eliminated and merged into about.js.

But in case you were curious what the Webpack 2.0 output is with minChunks disabled, here it is:

Version: webpack 2.2.1
Time: 21718ms
                                        Asset       Size  Chunks                    Chunk Names
       js/main_ba108242d5c525cd6d9d.bundle.js     325 kB       5  [emitted]  [big]  main
     js/catalog_bef2c4168fd2a654ff5c.chunk.js      36 kB       0  [emitted]         catalog
     js/finance_aa3df57fc02b5cf34182.chunk.js    1.16 kB       2  [emitted]         finance
     js/contact_d26e17e05dadd81271dc.chunk.js    1.04 kB       3  [emitted]         contact
       js/about_21fe64e0b5fde043e86d.chunk.js    1.05 kB       4  [emitted]         about
         js/how_1d3b0d4332d795dd1fa3.chunk.js  968 bytes       1  [emitted]         how
                          chunk-manifest.json  274 bytes          [emitted]
     js/vendor_a9ebdde44b1b55d21147.bundle.js     396 kB       6  [emitted]  [big]  vendor
css/main_70a9bee7273fab9ef275864d6bda5aca.css     205 kB       5  [emitted]         main
                                manifest.json  467 bytes          [emitted]
                                sw-toolbox.js    17.4 kB          [emitted]

(I’d make a table comparing the file sizes, but Medium doesn’t let you make tables…I know, it’s crazy)

Catalog.js went from to 41.9 KB to 36 KB, a 14% file size reduction


Overall we saw a slight reduction in file size, but nothing to write home about. That being said, our site is very small. If it were larger, then the file size reduction would’ve been more significant.

Was this worth the effort and opportunity cost of upgrading? From a business standpoint, probably not. But at least I got this blog post out of it.

(Here’s a link to this article on Medium)


Jeremy Bernier

Written by Jeremy Bernier who left the NYC rat race to travel the world, work remotely, and make the world a better place.