Code Splitting: Virtues and challenges

Fatema Bohra
Walmart Global Tech Blog
4 min readSep 17, 2019
Image by Gerald Friedrich from Pixabay

Everyday users visit hundreds of websites and only stay for a few minutes. According to Google’s research, 53% of visits are abandoned if pages take longer than 3 seconds to load. Shaving off even a fraction of a second of the web page load time results in a revenue boost.

With the advent of React and Webpack, it has become a common practice to bundle everything in a single package and serve it to users. This has a substantial negative impact for first-time users as they get served a boatload of unused code. We can improve our site’s performance if we are able to deliver only the pieces of code that’s relevant for that scenario. Code Splitting, if done correctly, can help in achieving this.

Two major approaches for code-splitting are -
1. React Lazy and Suspense
2. Loadable components

React Lazy and Suspense

This is React’s official method. Let’s start with an example.

Example — Single Bundle

Assume our main.bundle.js has two components Header and Filter. HeaderComponent is rendered on every page but that’s not the case with FilterComponent. It renders only when isFilterOpen switch is enabled. Components like these are ripe for separating out from main bundle to their own conditionally loaded chunks. So, let’s split it.

Example — Bundle Splitting with React lazy and Suspense

React.lazy() expects a function that calls a dynamic import() as its argument. Dynamic imports are handled by Webpack. This import puts our FilterComponent in a separate chunk which reduces the size of our main.bundle by 50 KB, and returns a Promise. React Lazy resolves this Promise to a React component only when there is a demand for it which in our example is controlled by isFilterOpen.

When isFilterOpen becomes true, it may take some time for the separate chunk to load. For that time interval we need fallback content as the filler, something that is similar to a loading screen. Suspense comes in handy here. It takes a fallback prop that serves as the filler.

That's it! you have split your component but wait you may run into a “Page-flicker” issue.

React Lazy provides a nice interface for code-splitting but sadly it only has client-side support. If you use React lazy for a website that also does server-side rendering, you would see a flicker effect when the website loads. This is because React does not know about all the split chunks that are required for hydration, and hence it loses the context that’s created by the server-side rendering.

If the server can provide the information about split chunks that were used during server-side rendering, and if we can make the React hydration wait for all the chunks to load, then we could avoid this flicker issue. This is precisely what Loadable Components does for us.

Loadable Components

React recommends using Loadable Components to split your components if you depend on server-side rendering. Loadable components consists of four parts —
1. Loadable Component function which works similar to React.lazy.
2. Loadable Server which works in conjunction with React DOM Server
3. Loadable Babel plugin for transforming code to enable sync loading on server and async loading on client.
4. Loadable Webpack plugin to generate an extended stats file that has information about all split chunk dependencies which is passed to server-side code.

Sample — Loadable Stats file
Example — Bundle Splitting with Loadable Components

You can see the similarity between React.lazy and Loadable Components in the example above. Apart from this, server-side rendering also needs to be modified to add support for Loadable Components.

Example — Server-side rendering with Loadable Components

As shown above, Loadable Components creates an extractor from stats file. This extractor creates script tags for the required chunks and passes them in DOM like shown below.

Now the information is available on client-side and we have to make our entry component wait for these chunks before hydration. This task is handled by loadableReady() function.

Example — Delaying hydration until Loadable Components is ready

The callback passed to loadableReady() is called once the required chunks are loaded. This delays the hydration of the MainComponent on client-side until our state is consistent with that of server-side, resolving the flicker issue.

We have successfully created a separate chunk but is it optimized?

Webpack’s Split Chunk plugin

Irrespective of your choice between React Lazy or Loadable, your split chunks are not always a disjoint set. It’s highly possible that your chunk may contain duplicate code. This instead of improving your performance may degrade it.

Webpack4 has a plugin called SplitChunksPlugin which can be used to remove duplicate code. I won’t be able to delve into details of the plugin in this blog post but would hopefully cover in a future blog post. Until then, you can read Webpack’s documentation about it.

I hope this blog post serves as a good starting point for your work on code splitting. Let us collectively reduce the JS bloat across the Internet. Happy splitting!

--

--