- 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.
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.
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.
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
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.
In the picture we see that the user waits for the 155KB chunk.
Now let compare it to the second code example.
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.
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
npm install -g source-map-explorer
Then run the command
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
Output of the command with the original React application:
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.
In the rest of the picture we see a treemap of all our code parts.
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.
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
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.
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.
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.
In the next article, I will talk about how to use compression to reduce data load time, hence improving performance.