Progressive loading for modern web applications via code splitting

Anton Lavrenov
5 min readJul 20, 2016

--

Are your users tired of waiting when your app is loading and they close the tab? Let’s fix it with the progressive loading!

I will use webpack for bundling and React for a demonstration.

I am compiling and bundling all my javascript files (sometimes css and images too) into ONE HUGE bundle.js. I guess you are doing this too, aren’t you? It is a pretty common approach for making modern web applications.

But this approach has one (sometimes very important) drawback: first loading of your app may take too much time. As a web browser have to (1) load large file and (2) parse a lot of javascript code. And loading can take really much time if a user has bad internet connection. Also, your bundled file can have components that user will never see (e.g. user will never open some parts of your application).

Progressive Web Apps?

One of the good solutions for better UX is Progressive Web App. Google this term if you don’t know it yet. There are tons of good posts and videos about it. So Progressive Web has several core ideas, but right now I want to focus on Progressive Loading and implement it .

The idea of Progressive Loading is very simple:

  1. Make “initial load” as fast as possible.
  2. Load UI components only when they are required.

Let us assume we have React Application that draws some charts on a page:

Chart components are very simple:

LineChart.js
BarChart.js

These charts can be very heavy. Both of them have react-konva as a dependency (and konva framework as a dependency of react-konva).

Please note that LineChart and BarChart are not visible on the first load. To see them a user needs to toggle checkbox:

Before/after toggling charts

So it is possible that the user will NEVER toggle that checkbox. And this is a very common situation in real world web application: when a user never opens some parts of the app (or open them later). But with a current approach, we have to bundle all components and all their dependencies into one file. In this example we have: root App component, React, Chart components, react-konva, konva.

Compiled, bundled and minified result.
Network usage.

280kb for bundle.js and 3.5 seconds for initial loading with a regular 3g connection.

Implementing Progressive Loading

How can we remove these chart components from bundle.js and load them later and draw something meaningful as fast as possible? Say hello to good old AMD (asynchronous module definition)! And webpack has good support for code splitting.

I suggest to define HOC (higher order component) that will load chart only when a component is mounted into DOM (with componentDidMount lifecycle callback). Let’s define LineChartAsync.js:

Then instead of

import LineChart from ‘./LineChart’;

We should write:

import LineChart from ‘./LineChartAsync’;

Let us see what we have after bundling:

We have bundle.js that includes a root App component and React.

1.bundle.js and 2.bundle.js are generated by webpack and they include LineChart and BarChart . But, wait, why is the total sum bigger? 143kb+143kb+147kb = 433kb vs 280kb from previous approach. That is because dependencies of LineChart and BarChart are included TWICE (react-konva and konva defined in both 1.bundle.js and 2.bundle.js), we can avoid this with webpack.optimize.CommonsChunkPlugin:

new webpack.optimize.CommonsChunkPlugin({
children: true,
// (use all children of the chunk)
async: true,
// (create an async commons chunk)
}),

Now dependencies of LineChart and BarChart are moved in another file 3.bundle.js, total size is almost the same (289kb):

Network usage on the first load
Network usage after showing charts

Now 1.75 seconds for initial loading. It is much better then 3.5 seconds.

Refactoring

To make the code better I would like to refactor LineChartAsync and BarChartAsync. First, let’s define basic AsyncComponent:

And BarChartAsync (and LineChartAsync) can be rewritten into simpler component:

But we can improve Progressive Loading even more! When application is initially loaded we can schedule loading of additional component on background, so it is possible that they will be loaded before user toggled checkbox

And loader.js will be something like this:

Also, we can define components that will visible on the first screen, but in fact loaded asynchronously later and a user may see beautiful placeholder while a component is loading. Please note that placeholder is not for API call. It is exactly for loading component’s module (its definition and all its dependencies).

Card component will be loaded later.
const renderPlaceholder = () =>
<div style={{textAlign: ‘center’}}>
<CircularProgress/>
</div>
export default (props) =>
<AsyncComponent
{…props}
loader={loader}
renderPlaceholder={renderPlaceholder}
/>

Conclusion

As a result of all improvements:

  1. Initial bundle.js has a smaller size. That means a user will see some working UI components faster.
  2. Additional components can be loaded asynchronously in the background.
  3. While a component is loading it can be replaced with some placeholder components .
  4. For exactly this approach Webpack is required. But you can use it not only with React, but with other frameworks too.

Take a look https://github.com/lavrton/Progressive-Web-App-Loading for full source and webpack configurations.

Do you have performance issues with your web-app? I can help you.

--

--