Code Splitting in a React Application
Code splitting is a useful technique to reduce the size of the initial download and keep your app users engaged.
Dec 20, 2019 • 7 Minute Read
Introduction
One of the biggest problems when developing a relatively complex Single Page App is the size of the code bundle that needs to be downloaded by the web browser. Even when code is minimized and gzipped it will often be larger than the recommended maximum file size to give a good user experience and not leave your users looking at a blank window or, even worse, deciding to visit another site.
Apart from simply writing less code, code splitting is a useful technique to reduce the size of the initial download and keep your users engaged. This guide will show how to build React components that enable you to split both components and packages using both the higher order component patter and Suspense in React version 16.8 and higher, and how to use code splitting outside of a component.
Dynamic Import
Modules can be statically included in an application using the import statement. Anything added in this way will be included in the main code bundle. To use a module without including it in the main bundle, you can use the import() function. This function is similar to the statement in that it accepts a module as a parameter but it will only import the module when it is accessed by the application. When bundlers such as webpack and parcel are creating code bundles they will put these modules in separate files rather than the main bundle.
Unfortunately it isn't quite as simple as calling import() and immediately getting the module returned. Dynamically importing potentially has to get a file from a server so it returns a promise that resolves to the module rather than the module itself. This means that to access the module the application will have to wait until the promise has been resolved.
Import a Component
The most obvious way to implement code splitting is to dynamically import at a component level. This does not mean every single component should be dynamically imported, however. Rather, those that use a large package (unused anywhere else) would be a good candidate. Also when using routing, it is a good idea to dynamically import each page, except for the home page.
To dynamically import a component, you can use a variation on the higher order component pattern. A higher order component accepts a component as a parameter and returns a component. In this case you will accept a promise that resolves to a component and returns a component. This higher order component can define a state variable to contain the rendered component that is initialized to null like this:
this.state = { Component: null };
Then inside the higher order component constructor the promise that has been passed as an argument can be executed and, when it resolves, the state can be updated with the resulting component:
componentImport().then(loaded => this.setState({ Component: loaded.default }));
All that is left to do in the higher order component is implement the render life-cycle method. Inside this method we can test the state for null to check if the component has been loaded. If it hasn't we can either return null or something to inform the user that the application is waiting. If it has loaded we can render the component passing in the props. The final higher order component will look like this:
function DynamicComponent(componentImport) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = { Component: null };
componentImport()
.then(loaded => this.setState({ Component: loaded.default }));
}
render() {
if (this.state.Component === null) {
return <div>Loading component, please wait</div>;
}
return <this.state.Component {...this.props} />;
}
};
}
We consume this higher order component using the import() function as a parameter and then render the resulting component like this:
const DynamicImportComponent = DynamicComponent(() => import("./ShowUtc"));
return <DynamicImportComponent />;
Import Using Suspense
In React version 16.8 the first part of the Suspense API was introduced which added the ability to dynamically import a component using the lazy function. This works in a similar way to the higher order component above. However, whatever is rendered while waiting for the component to load is defined as part of a Suspense parent component using its fallback property. So to do the same as in the higher order component above, you need to do the following:
const LazyComponent = lazy(() => import("./ShowUtc"));
return (
<Suspense fallback={<div>Loading component, please wait</div>}>
<LazyComponent />
</Suspense>);
The Suspense component does not have to be the immediate parent of the component created using lazy. React will go back through the component tree to find the first instance of Suspense and use that. If a component using lazy is rendered with no Suspense component as a parent anywhere in the tree, React will throw an error.
Dynamically Import Functionality
The two examples above deal with dynamically importing at the component level which is often required in a React application. If you are using a particularly large module inside a function that is rarely used, however, you may want to dynamically import that module from inside the function so that the code for the module will only be downloaded when the function is called. This can also be done using the import() function but rather than using component state to store the module we can make the function using the module a promise and simply await the result of the promise and use it. For instance this function imports the moment package and uses it once it has loaded:
async function getUtcData() {
const moment = (await import("moment")).default;
return {
utc: moment()
.utc()
.format(),
offset: moment().utcOffset(),
};
}
The function is consumed like this:
const { utc, offset } = await getUtcData();
Conclusion
Code splitting can drastically improve the load times of single page applications by only downloading code from the server at the point it is needed. This guide has shown that React and Javascript provide both patterns and APIs to simplify this for developers. A sample application using each of the techniques in this guide can be found here.