How to Make the Loading Bar Disappear: A Complete Guide to Code Splitting in React

Brittani Élan Taylor
4 min readDec 3, 2020

An overview of Code Splitting in React.js and High Order Components (Loadable + Lazy)

Photo by Aziz Acharki on Unsplash

Background/Bundling

When first starting a new project in React, many of us used such tools as Create React App, Next.js, or Gatsby. Using these tools, files are “bundled” using tools like Webpack, Rollup or Browserify. Bundling is the process of following imported files and merging them into a single file: a “bundle”. This bundle can then be included on a webpage to load an entire app at once.

Why do we need bundles?

Bundles were initially created to optimise the number of HTTP requests it would have taken to download multiple files. Additionally, they enable the browser to run your application like it was written using just JavaScript.

What does this mean for your imports?

ECMAScript modules are completely static: you must specify what you import and export at compile time and can’t react to changes at runtime.

STATIC

The static structure of imports is enforced syntactically in two ways.

import module from './dir/module';
  1. The import declaration can only appear at the top level of a module.
  2. They only accept a string literal as the module specifier.

Code Splitting overview

Yup, this is exactly what we’re doing.

Instead of downloading the entire app before users can use it, code splitting allows you to split your code into smaller, more manageable chunks which your browser can then load on demand.

Code splitting is supported by the dynamic import(). The import() function-like form takes the module name as an argument and returns a Promise.

The proposed operator for loading modules dynamically works as follows:

const moduleSpecifier = './dir/someModule.js';
import(moduleSpecifier)
.then(someModule => someModule.foo());

The operator is used like a function:

  • The parameter is a string similar to the import declarations most of us currently use. However, with dynamic import(), the parameter can be any expression whose result can be coerced to a string.
  • The result of the “function call” is a Promise. Therefore anything called within then can be executed once the module is completely loaded.

Use Cases

There are three main use cases for code splitting.

1) Loading code on demand

Code can be loaded based on user actions, such as clicking on a link or scrolling to a certain position on the page.

2) Conditional loading of modules

Sometimes you may want to load a module depending on whether a condition is true.

3) Computed module specifiers

Some variables aren’t set until runtime or may change based on state/props. In this case, an interpolated string would work for the module specifier unlike in static import declarations.

High Order Components for Code Splitting

There are 2 primary high-order components used for code splitting: Lazy and Loadable.

Lazy

The React.lazy function lets you render a dynamic import as a regular component. This also handles router-centric code splitting.

There’s one main limitation you might notice above.

React.lazy currently only supports default exports.

You can find an easy enough workaround for that here. Additionally,

React.lazy and Suspense are not yet available for server-side rendering. If you want to do code-splitting in a server rendered app, Loadable Components is recommended

Well luckily for you…

Loadable

According to the Loadable documentation:

Sometimes components load really quickly (<200ms) and the loading screen only quickly flashes on the screen.

A number of user studies have proven that this causes users to perceive things taking longer than they really have. If you don’t show anything, users perceive it as being faster.

Accordingly, there is a lot of extra functionality for modifying behavior based on set time intervals. Examples below.

Fun facts

  • import() can be used from scripts, not just from modules.
  • If import() is used in a module, it can occur anywhere at any level, and is not hoisted.
  • import() accepts arbitrary strings (with runtime-determined template strings shown here), not just static string literals.
  • The presence of import() in the module does not establish a dependency which must be fetched and evaluated before the containing module is evaluated.
  • import() does not establish a dependency which can be statically analyzed. (However, implementations may still be able to perform speculative fetching in simpler cases like import("./foo.js").)

--

--