Understanding Code Splitting and Bundling in React

·

7 min read

Understanding Code Splitting and Bundling in React

Introduction

When building a complex React application, managing the size of the JavaScript bundle becomes crucial for optimizing the performance. Code splitting is a technique that allows us to divide the application's codebase into small chunks, and load them on demand, as needed. This approach offers several benefits, including improved loading times, reduced initial bundle size, and enhanced user experience.

In this blog post, we will explore the concept of code splitting in React, and delve into techniques and best practices to implement it effectively in your projects. We will examine manual splitting, React's React.lazy() and Suspense API, as well as route-based code splitting with React Router.

Let's dive in!

Understanding code splitting

What is bundling?

When we create a web application, we write code using various programming languages like JavaScript, HTML, and CSS. This code is organized into separate files, each containing different parts of our application's functionality. These files are known as bundles, and they are served to the browser for the application to run.

Now, bundling is the process of putting all these files into one or a few files. It's like putting all the puzzle pieces of our code together to create a complete picture. Instead of serving multiple individual files, bundling allows the application to load a single file or a few files, making the initial loading efficient.

When bundling a React app several tools and techniques can be used. One popular tool is Webpack, a module bundler that optimizes code and bundles the assets for deployment. Other tools like Rollup and Parcel are also commonly used for bundling React applications.

When we create our React app with create-react-app(CRA), it sets up a bundler called Webpack for us under the hood.

Example:

In the following example, we are importing an add function from a math.js file. During the bundling process, this will be replaced by the actual function.

// app.js
import { add } from './math.js';

console.log(add(16, 26)); // 42
// math.js
export function add(a, b) {
  return a + b;
}

This is the bundle that we get after replacing the import statement with the actual code:

function add(a, b) {
  return a + b;
}

console.log(add(16, 26)); // 42

What is code splitting?

As the size of our application grows, our bundle size grows, too. Especially, when we add a large third-party library. We need to keep this in mind. Otherwise, the bundle size will be large, and our app will take a long time to load.

To avoid the large bundle size, we should start splitting our code as the app grows. Bundlers like Webpack, Rollup, and Browserfy support code splitting. They can create multiple bundles that we can load dynamically as needed.

Once the app is split into multiple bundles, we can use the "lazy-load" to load the bundles as the users' requirements.

Techniques for Code Splitting in React

There are several code-splitting techniques available in React. Let's explore a few popular approaches:

  1. Manual code splitting

  2. React.lazy() and Suspense

  3. Route-based Code Splitting

1.Manual Sode Splitting

Manual code splitting involves manually splitting the code into chunks using dynamic imports. With dynamic imports, we can specify which parts of our code should be loaded dynamically. It allows us to control when and where the code is fetched from the server.

Here is an example of manual code-splitting:

import React, { useState } from 'react';

export default function MyComponent () {
  const [isComponentLoaded, setIsComponentLoaded] = useState(false);

  const handleClick = async () => {
    // Dynamically import the component when the button is clicked
    const dynamicComponent = await import('./DynamicComponent');
    setIsComponentLoaded(true);
  };

  return (
    <div>
      <h1>My Component</h1>
      <button onClick={handleClick}>Load Component</button>
      {isComponentLoaded && <DynamicComponent />}
    </div>
  );
};

In this example, the component MyComonent dynamically loads another component called DynamicComponnet, when the Load component button is clicked.

The dynamic import is achieved through the import() function which is a Javascript feature that allows us to asynchronously load modules. It returns a promise. In our example, we are importing DynamicComponent from the './DynamicComonent' module.

Here is our dynamic component:

// DynamicComponent.js
const DynamicComponent = () => {
  return (
    <div>
      <h2>Dynamic Component</h2>
      <p>This component is dynamically loaded!</p>
    </div>
  );
};
export default DynamicComponent;

When the button is clicked, DynamicComponent loads asynchronously and renders on the page.

2.React.lazy() and Suspense

React's React.lazy() function and Suspense component works together to enable code splitting and lazy loading of components in a React app.

Let's see how they work with an example:

import { lazy, Suspense } from 'react';

// Lazy-loaded component
const LazyComponent = lazy(() => import('./LazyComponent'));

export default function App() {
  return (
    <div>
      <h1>Lazy Loading Example</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  );
};

In this example, The App component renders a lazy-loaded component called LazyComponent.

The React.lazy() function allows us to lazily load components, meaning the component will be loaded only when it's actually rendered in the application.

The React.lazy() function accepts a function that returns a dynamic import of the component module. In our example, we use import('./LazyComponent') to specify the location of the module to be loaded lazily.

When the LazyComponent is rendered, React will automatically initiate the loading process. While the component is being fetched, the Suspense component will display the fallback UI. Once the component is loaded, React will render it in place of the fallback UI.

Here is the LazyComonent module that is dynamically imported:

const LazyComponent = () => {
  return (
    <div>
      <h2>Lazy Component</h2>
      <p>This component is lazily loaded!</p>
    </div>
  );
};
export default LazyComponent;

When the App component is rendered, the LazyComponent is not loaded immediately. It's loaded only when the component is actually rendered in the application. When the app starts, only the App component is present in the bundle. The LazyComponent is fetched when it gets its turn to render in the application.

3.Route-based Code Splitting

Route-based splitting involves splitting our code based on different routes in our application. By leveraging the router libraries like Reat router, we can load the code for specific routes on demand, and reduce the initial bundle size.

With route base splitting, each route can have its chunk and it allows users to load the code for a specific route. This technique is particularly useful for applications with a large number of routes.

Let's understand this with an example:

import React, { lazy, Suspense } from 'react';
import {
 BrowserRouter as Router,
 Route,
 Switch } from 'react-router-dom';

// Lazy-loaded components for different routes
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
const Contact = lazy(() => import('./Contact'));
// ...

const App = () => {
  return (
    <Router>
      <div>
        <Suspense fallback={<div>Loading...</div>}>
          <Switch>
            <Route exact path="/" component={Home} />
            <Route path="/about" component={About} />
            <Route path="/contact" component={Contact} />
            {/* ... */}
          </Switch>
        </Suspense>
      </div>
    </Router>
  );
};
export default App;

In this example, The App component uses the React router library to implement different routes for our application. Each route corresponds to a specific page or functionality.

We use the lazy() function from React to lazily load the component for each route. For example, Home, About and Contacts are lazy-loaded components, and dynamically imported with import(). This splits the code into separate chunks for each component.

The Suspense component wraps our routes and provides a fallback UI that is displayed when the lazy-loaded component is being loaded.

By lazy loading the components for each route, we ensure that only necessary components are loaded when the corresponding route is accessed. As a result, the initial bundle size is reduced, and loading time is optimized for each route.

Conclusion

Code splitting is a powerful technique that allows us to optimize the performance and loading time of our React application. By splitting the code into smaller, more manageable chunks, and loading them on demand, we can significantly reduce the initial bundle size and improve user experience.

In this post, we explored various aspects of code splitting. We learned what is bundling, what is splitting and the benefits of code splitting.

we have explored different techniques for code splitting in React applications. We discussed manual splitting, splitting with the React.lazy() and Suspense and router-based splitting.

We learned that incorporating code splitting into our React development workflow will significantly enhance the performance of the applications.


References