TL;DR

  • The main factor that affects web performance is the JavaScript processing time.
  • Code splitting is a key factor to reduce your website load time.
  • Dynamic import helps you to split your code into smaller chunks that will be loaded to the user at the same time, but the browser will parse / execute the main files first though rendering the content without delay.
  • Lazy loading helps you to cut your code into small chunks that will be loaded to the user at need, hence only needed JavaScript will be loaded and executed

Some ground base theory

Why do we care at all?

Why do we care about our website load time in the first place?

Web pages load time plays a heavy role in search engine optimization, simply put if you want your website to appear in Google's first results, reducing it will help you attain that.

Also, a performant website will load faster and grant a better user experience.

In this article, we will delve into the subject and illustrate the use of code splitting to maintain a good score in web performance.

To reduce your website load time you can attack multiple aspects, like using compressed images, using default font to display text while your website's fonts are loading, Gzipping HTTP requests and making use of browser's cache. But the key factor to reduce your website load time is reducing the JavaScript processing time, and that what we will discuss in this article.

Bundling …

Once you’ve done developing your application, and getting ready to deploy, chances are you’re using a bundler to create a single or multiple JavaScript files that contains all the code you need and its dependencies. For more details on modules bundling.

It's worth noting that a bundler will also execute a series of optimisations like minification and obfuscation which make your source code harder to read and adds a layer of protection from stealing.

As your application grows, your bundle size gets bigger as well, takes more time to transfer to the client and then longer to be executed. For large applications, this becomes a serious problem.

In the next chapter we will illustrate how code splitting solves this problem. By bundling your source code into multiple independent files - called chunks, these files can be downloaded separately, and serve only the code you need to the client not all the application at once.

For the rest of this article we will stick to webpack as a bundling tool.

Code Splitting

We should note here that, the bundler is the one taking responsibility to split the source code. Knowing that, webpack offers three methods to split your code: Entry Points, Prevent Duplication, and Dynamic Imports.

Entry Points : consists of manually splitting the code by specifying multiple entry points in webpack.config.js file.

Prevent Duplication : allows you to create chunks for modules shared between multiple entry points, so you don't have duplicate code in two chunks.

Dynamic Imports: allows us to split code via inline function calls within modules.

In this article, we will use the third method in coordination with React. For more details on the first two methods.

Into practice - webpack in action with a React application

In this example, we’re going to build a basic two pages React application to illustrate the impact of code splitting.

Our React application contains a home page and a signup page, the signup page is rendered by a component that also loads a heavy dependency.

To optimize our React application, we don’t want the user to download all the signup service in his first visit to the landing page.

First, we have an App.jsx file where we import two page components, Home and SignUp and rendering them using react-router.

In the second code we import the two components using React Lazy. The lazy statement returns a promise. We pass the promise component to BrowserRouter’s component prop.

We also need to wrap the lazy components with a Suspense component, that will render any desired content while the lazy component is loading, you can pass your desired content to Suspense with the fallback prop.

When we serve the static files of the first code example, without code splitting, we can see that the user downloads the whole React application code, waits for JavaScript parsing, only then he sees the page content.

Network view of loaded JS files for build_all

In the picture we see that the user waits for the 155KB chunk.

Now let compare it to the second code example.

Network view of loaded JS files for build_lazy in landing page

In the second picture, the user waits for a chunk of only 48KB, less than a third of the first load size, therefore less load time.

Only when we navigate to the signup page, then the rest of the code is downloaded. In the following picture, this corresponds to chunk 4 and 5.

Network view of loaded JS files for build_lazy in signup page

Explore your bundles with source-map-explorer

source-map-explorer is a developer tool for the JS bundles analysis. It uses  the source maps to determine from where each bit of your code came from. It also shows you a treemap visualization of your code.

To use it you can quickly install it globally

yarn global add  source-map-explorer

Or

npm install -g source-map-explorer

Then run the command

source-map-explorer your_build/static/js/*

A tab will open in your browser showing a visualization of your bundles details.

To illustrate the use of source-map-explorer, I generated the build for the simple React application before and after using react lazy and copied the files into two folders, named build_all and build_lazy.

Output of the command with the original React application:

source-map-explorer output for build_all

Let's analyse this part by part

The bundle list contains all our js files and their sizes. As you can see, all our code is mainly in one file as no code splitting took place.

React App bundle list

In the rest of the picture we see a treemap of all our code parts.

Treemap of bundle content

We can see here that the library libphonenumber.js takes 77.4% of our code, and we only need it in the one page, but we load it in all other pages. This may be a path for improvement.

For reasons of comparison, here’s the output of source-map-explorer after the use of React Lazy.

source-map-explorer output for build_lazy

Now, the code containing libphonenumber.js has been put in a separate bundle that will be loaded when needed.

Dynamic import JS

  • What’s dynamic import ?

Unlike static imports, the dynamic import() statement returns a promise that resolves into a module object. When bundling your code with webpack, once it comes across a dynamic import statement, it creates a new chunk for it.

  • Code example

We can change the code where we use libphonenumber as follows:

Using source-map-explorer we get

source-map-explorer output for build_dynamic

Pretty similar to what we got using React Lazy; one big chunk containing libphonenumber.js and another chunk containing the rest of the React application’s code.

Network view of loaded JS files for build_dynamic

With this method, we do load both chunks. That may seem useless, but in fact the first chunk is downloaded, parsed and DOM elements loaded before the browser sends a request to download the second chunk. So we effectively solved the same problem with another solution.

Conclusion

With the code splitting approach, as presented in this article, we will inevitably load the code for React library and the browser will parse it and execute it. We could also handle this by using Server Side Rendering, executing all React code in the server side and only send to the user the static HTML, CSS, JS.

We have tackled reduction of JavaScript execution time, which is one of the main aspects of Web Performance. We should not forget about our data load time, such as images, users' data and so on...

In the next article, I will talk about how to use compression to reduce data load time, hence improving performance.