Code Chunking with Webpack — A pragmatic approach

Param Singh
React Weekly
Published in
7 min readSep 3, 2016

Ever since the inception of client side JS taking control of the web, we have observed a hefty amount of JS being written or included in our web apps. Gone are the times when our servers used to render the html for our web apps (although they’re used for initial server side rendering). Today we need more out of our web apps like lazy loading of data through Ajax, cool animations, app state management, routing, infinite scrolling and many more.

All of these things mentioned require a decent amount of work on JavaScript by a front-end developer. The result is a huge pool of JS files since developing for front-end requires being dependent on some library or framework or at least jQuery. Writing all of your code in VanillaJS is not everybody’s cup of tea. Besides this, you also tend to keep your code modular using design patterns namely modular pattern, factory pattern, singleton pattern, observer pattern etc. These things takes an important place in development. I can’t imagine engineering a complex web app without the use of such design patterns.

Now, while maintaining a modular code is beneficial for development purpose but not ideal for your production environment. It’s a very crucial step in web optimisation to bundle up all your files into one before deploying on production. Why? because generating individual http network request for all those modular files is not efficient. Running 1 request for a bundled file over 100 requests to fetch 100 files is more efficient. So, as you can see there’s a trade off kinda situation between the two approaches. The latter approach can provide you with things like lazy loading of modules on demand but results in a lot of network requests. The former approach does efficiently single request and reduces frequent roundtrips to the server but at the same time takes a heavy toll on the initial load time. The CRP(Critical Rendering Path) of your web app should not involve long blocking Javascript as it might block your initial load time giving a bad experience to your first time users and even worse on a slow network.

So, one thing is clear that we just have to bundle up the JS files for production while keeping the initial load time low. Then how can we break this deadlock? The answer is Code Splitting or Chunking i.e we can find a middle ground between these two things by increasing the number of requests while keeping them bundled.

My discussion will follow my favourite bundling tool webpack. But this concept of chunking is there in all other bundlers like browserify, requirejs or almondjs. Coming back to the discussion, for most of our web apps we have two kind of codes, one which is written by us and the other on which we depend or the third party libs you can say. Now, its wise to separate them apart firstly. Reason I’ll tell you shortly.

Before Segregation
After Segregation

Above are the two illustrations of my bundled file sizes before and after chunking. As you can see my index.js has size of 666 kB(after minification) in the first screenshot and when I moved all my vendor or 3rd party code to vendor.js it became 1.1 MB + 172 kB. This inflation is because I have included the whole Material UI library for my react project instead of individual components as in the first case. But lets not deviate out discussion on that. Sticking to the point, we have successfully finished the first phase of chunking.

Webpack Config

Lets, do a bit more. Lets say I wanna cache this vendor.js on user’s client machine for a longer period of time using maybe service worker, server side caching or through shipping it along with my Android app build as I do at my current job. The point is, our vendor.js is not going to change that frequently as compared to my own written code in the form of index.js. Moving on, we have to do versioning to make sure our vendor.js file doesn’t change overtime. Also for generating a hash based content for our vendor.js, we’ll need to separate our webpack runtime code from it into say meta.js or init.js. See this github issue. The result will look something like this.

Also, to make your vendor.[chunkhash].js fully static, please use this new webpack NamedModulesPlugin since it uses the names of module files instead of dynamically generated files during bundling. These are all little caveats I figured out while working with webpack.

Cool, so far we have separated the runtime from our vendorjs, content based automated versioning has been done. Our build files are ready for caching. Only one teensy things left, infact the main challenge, how will we include these dynamically generated hashed filenames into my index.html?? Don’t worry, there exists a webpack HtmlWebpackPlugin that’ll exactly solve this problem. You can use it like this

As you can see I’m using an ejs template for generating my index.html file. You can skip that and set your inject option as true which will automatically inject the generated js files in the end before the closing body tag. My requirement was different since I had to embed my versioned files in a Django template which already has {{}} reserved, otherwise I could had used moustache or handlebars. You can checkout more options on HtmlWebpackPlugin docs.

Voila! We just accomplished transforming our web app ready for caching 3rd party vendor.[chunkhash].js. Now, all that will change overtime is index.[hash].js. So, 90% of the load time has been already reduced for subsequent page loads on users machine as the hash of the vendor file will remain same through out. Now, all that we need to focus is on index.[hash].js which is 172 kB in our case. We can further break this into chunks and load them on demand on events like route change most commonly.

Lets try it out with react-router. React Router allows you to add hooks to routes before navigating. So what you can do is specify the load of a certain module using require.ensure or System.import with webpack 2. Lets stick to the former one.

Before
After

Above is the implementation of lazily loading the route. On a side note, I tried abstracting the getComponent handler function but for some bizarre require context reasons it wasn’t accepting dynamic params from the closure (If you can solve it, please go ahead and let me know as well). Anyway, lets see run the webpack and analyse the output.

The size of the index.[hash].js has reduced from 173 kB to 113 kB. There’s a straight ~35% or reduction in its size, so you can pretty much expect quicker initial load than before. And as you navigate to a certain route, the corresponding chunk will be load lazily on demand.

Dynamic loading of chunks based on routes

Phew! That was tiring. Was it? You decide. But in the end of the day your app users will be happy which is makes our efforts worthy. So, go ahead and transform your web app taking these amazing advantages webpack offers you to put a smile on your app users face.

Summary

So, we learnt about why bundling is important, how to separate your vendor and your own app code to implement caching, versioning your files for proper cache busting. I’m sure you might have got stuck in a situation like “I have deployed my latest code on server, but still I’m getting the old version on my app”. Webpack automated versioning we learnt can help you alleviate such pains (things like index.js?v=2). After that, our separated index file of 173 kB was reduced to 113 kB using lazy loading with react-router.

I hope you liked my post. I thoroughly enjoyed writing it and in fact learnt few things while writing it. If you do too, please recommend and comment.

Thank You!

--

--

Param Singh
React Weekly

Senior Front-end Engineer at AWS, Ex-Revolut, Flipkart. JS enthusiast. Cynophile. Environmentalist