Code splitting with React & Webpack: advanced app optimisation
Tomasz KajtochFrontend Developer
When the code base grows, your app tends to slow down. To defend performance, developers arm themselves with various optimisation techniques. Unfortunately, a lot of them reach their limit pretty quickly. But there is one that can let you go beyond conventional restrictions. Let’s get the gist of code splitting with React & Webpack.
Your single page application is growing. Every week, new features are being added and its size increases. You want to provide great user experience, but downloading a lot of megabytes of the application’s code just to display a single screen doesn’t seem right. Sure, you can enable code minification and compression, or caching mechanisms, but eventually you’ll end up having the same problem – a large amount of data required to download to initialise your application.
When is it worth it to use code splitting? In particular:
when you need to initialise the app quickly, without using too much processing power (e.g. for mobile devices),
or when the application has multiple permission groups or sections visible only to specific users.
As well as in many other cases.
The term “code splitting” may sound quite fancy and advanced. In fact, it’s pretty straightforward and it only takes a while to understand the basics and start implementing.
When should we use code splitting?
To understand when we should use code splitting, it’s helpful to introduce the term Time-to-Interactive (TTI). It’s a value that specifies the time required to load all assets essential for the website to initialise and become interactive for the user. Unfortunately, when it comes to SPAs, TTI is often too long for the user to patiently keep waiting. Various surveys by Akamai and Gomez.com conclude that almost half of all users expect the page to load in under 2 seconds. Extending it by another second compels the user to abandon it.
To provide excellent user experience, we should try to make TTI as low as possible. We’re going to achieve that with code splitting.
Analyse first which modules are necessary at all times and which you can load asynchronously when a user enters specific routes. It will help you understand the performance aspect of your application and optimise it to a greater extent.
Does anyone actually use code splitting?
Many companies use code splitting to make their products load faster. Let’s analyse a couple of the most popular websites:
Biggest social media brands search for all kinds of ways to speed up their apps
The entire Instagram production codebase has the size of a little over 2 megabytes. Thanks to code splitting, when a user enters the homepage, only a quarter of that, necessary for the homepage to display, is loaded for a logged-in user.
Code splitting implementation details:
uses Webpack as a bundler,
code is split into 15 chunks:
3 main chunks contain the code and external libraries for the entire application,
1 translation chunk,
11 code chunks load on-demand – for the homepage, profile, post, tags, location pages and more,
stylesheets and small images under a few kilobytes are bundled directly with chunks.
Google itself uses code splitting to boost the speed of their most popular products
The popular YouTube desktop app is in fact a group of multiple connected applications. All the main views, meant for browsing and viewing videos, comprise the main app. Homepage, playlists, video page, comments, user profile, and others pieces have over 1.2MB in total, but to play videos you need as low as half of that. Other chunks are loading on-demand.
For most of views, YouTube uses Polymer. However, the upload view is using Angular 1.6.4. Its bundle contains the uploader logic. Angular itself has over half a megabyte in size.
Benefits of using code splitting
The most important code splitting benefits include:
Drastic reduction of the data which you need to download and parse in order to make the application interactive (achieving much better Time-to-Interactive).
Physical separation of different parts of the application and being able to decide when a browser should load them.
The ability to exclude administrative code for regular users to optimise file size and hide potentially sensitive data.
Client-side caching improvements achieved by updating only those parts of the application which have actually changed.
Of course, all technologies have their downsides and code splitting is no different:
It may sound obvious to you, but you should keep in mind that code splitting requires downloading some data after the page initialises. When the internet connection is down, there’s no way to load additional modules. Be ready for this and prepare an appropriate message for the user.
You also need to write additional logic to load multiple modules when they are required. However, thanks to Webpack, implementing it is as easy as using one function called import()!
Each bundle needs to have a header code that allows it to be injected into the application. For Webpack in the production mode, it’s only 80 bytes per file.
There is a slight delay caused by the need to load additional code chunks (but only during the first load).
If you’re ready to try code splitting in your project and you use Webpack or Browserify, all the code required to load modules asynchronously is already there – implemented and ready to use!
Code splitting with React – configuration and implementation
Please note that examples below are presenting code splitting with React as the library of choice and Webpack 4. If you are running an older version of Webpack, you’ll need to modify the configuration a little. For details, please look at the proper documentation. However, your choice of libraries and framework is not relevant here – code splitting will work all the same.
Code splitting is one of Webpack’s most notable features
Pre-requirement: separating vendors
Before splitting the actual code, we should start with excluding vendors (basically files from the node_modules directory) from the application bundle. Since the vendor code is not updated as frequently as the application code, it’s a good idea to prevent it from downloading every single time and keep it in the browser’s cache instead.
Webpack 4 doesn’t require any external plugins to make this happen. Edit your Webpack configuration file and add the optimisation key to it:
This section creates a cache group named vendors. It groups all modules that match the regex passed in the test key which, in reality, is matching all files in the node_modules directory.
When you compile this project, you get another file named vendors.js. You should add it to the index HTML file.
In this step, we use a fake router-8000 module, which creates routes inside your application. The typical method of creating routes would be:
Now, let’s make it run asynchronously, using dynamic imports and our code splitting functionality:
As you see, not much has changed. The getRouteComponent is asynchronous and uses async/await to wait for a Promise returned from import() to resolve. Take a look at the import call – besides the path to the component that contains our route and gets a proper exported module, it has a comment block inside. This comment block is a “magic comment” that Webpack utilises to decide to which chunk it should put the code that it imports. The value between quotes defines a name of the chunk. If you are curious, go to the Webpack docs to discover what the other magic comments are.
And that’s it! You’ve successfully created an asynchronously loading route, which the code fetches on demand.
You can check it out live or clone and try it yourself here.
We already know how to load chunk asynchronously by calling the import() function and utilising the Promise it returns. What should we do to prefetch the data before it’s needed, without having to display any loading screen and making the application freeze for that time?
It’s much easier than you might have thought! The first thing that import() does is check if the requested module is not already loaded or in the process of being so. If this is the case, Webpack uses the data from its cache to resolve the import without wasting time to fetch it again. Therefore, the easiest way to preload any module from its chunk is to call the same import() as in our routes configuration. In this case, we don’t need to do anything with the returned Promise.
Keep in mind that chunk names have to be the same in all the places where you want to use an asynchronously imported module. To make it DRY (in accordance with the Don’t Repeat Yourself principle), consider creating a factory module that calls import().
Advanced Webpack configuration and further reading
If you are curious about how Webpack handles its implementation of dynamic imports to offer code splitting functionalities, or you want to try other configuration options, see Webpack docs.
If you want to know what other features the Webpack 4 optimisation plugin has, take a look at the docs where everything is well-documented.
I hope you’ve learned by now how code splitting with React (or any other library you like for that matter) works, what its benefits and downsides are, and how to use it yourself. If you have any questions, feel free to ask them in the comment section below.
Full-stack developer and front-end specialist with an eye for strongly typed languages. He spends most of the time developing efficient and highly scalable web apps and contributing to a variety of open source projects. You don’t have to ask him twice to present at a conference or meetup either. When he doesn’t do, think or dream about coding, he enjoys photography and electronics.