ReactJS : lazy loading large libraries

07 Nov 2019

We tend to use different external libraries for various purposes. The size of those libraries varies from small/medium/large. What happens when you want to use a large library only for a particular route?

It doesn’t make any sense to load that library along with the initial bundle or with the vendor. Such large libraries are needed only when a user navigates to that particular route.

This blog post will discuss how we can achieve this in a ReactJS application

React lazy load heavy libraries

Image by Stijn Swinnen

For this blog post, let’s take the highcharts as the heavy library.

Without Lazy load

If you add basic highcharts it will be ~150KB (gzipped). So without lazyload, you will be shipping this 150KB in the main bundle itself. You can see this in action on now.sh and code is on github.

In this, we wrote Chart component which will be used for any highcharts usage in the project. This component is already set with default options needed for the Charts for the whole project.

// Chart.jsx
import React from 'react';

import Highcharts from 'highcharts/highstock';
import HighchartsReact from 'highcharts-react-official';
import noDataToDisplay from 'highcharts/modules/no-data-to-display';

noDataToDisplay(Highcharts);

class Chart extends React.Component {
  getDefaultOptions() {
    return {
      credits: {
        enabled: false
      },
      noData: {
        position: {
          x: 0,
          y: 0,
          align: 'center',
          verticalAlign: 'middle'
        }
      }
    };
  }
  render() {
    const options = {
      ...this.getDefaultOptions(),
      ...this.props.options
    };
    return <HighchartsReact highcharts={Highcharts} options={options} />;
  }
}

export default Chart;

Now when we need a PieChart we will use this Chart component and override the options.

// PieChart.jsx
import React from 'react';

import Chart from './Chart';

class PieChart extends React.Component {
  getOptions = () => {
    return {
      chart: {
        plotBackgroundColor: null,
        plotBorderWidth: null,
        plotShadow: false,
        type: 'pie'
      },
      title: {
        text: 'Browser market shares in January, 2018'
      },
      tooltip: {
        pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>'
      },
      plotOptions: {
        pie: {
          allowPointSelect: true,
          cursor: 'pointer',
          dataLabels: {
            enabled: true,
            format: '<b>{point.name}</b>: {point.percentage:.1f} %'
          }
        }
      },
      series: [
        // data
      ]
    };
  };

  render() {
    return <Chart options={this.getOptions()} />;
  }
}

export default PieChart;

bundle without lazy load

And when you look into the network tab the whole bundle is downloaded even though it is not required.

network without lazy load

With Lazy load

Next we will be converting this to lazyload the chart component. To do this we don’t have to change anything in Chart.jsx.

The only change will be in PieChart.jsx

// PieChart.jsx
import React from "react";

-import Chart from "./Chart";
+const Chart = React.lazy(() =>
+  import(/* webpackChunkName: 'chart' */ "./Chart")
+);

+const Loader = () => {
+  return <div>Loading...</div>;
+};

class PieChart extends React.Component {
  getOptions = () => {
    return {
      chart: {
        plotBackgroundColor: null,
        plotBorderWidth: null,
        plotShadow: false,
        type: "pie"
      },
      title: {
        text: "Browser market shares in January, 2018"
      },
      tooltip: {
        pointFormat: "{series.name}: <b>{point.percentage:.1f}%</b>"
      },
      plotOptions: {
        pie: {
          allowPointSelect: true,
          cursor: "pointer",
          dataLabels: {
            enabled: true,
            format: "<b>{point.name}</b>: {point.percentage:.1f} %"
          }
        }
      },
      series: [
        // data 
      ]
    };
  };

  render() {
-   return <Chart options={this.getOptions()} />;
+   return (
+        <React.Suspense fallback={<Loader />}>
+            <Chart options={this.getOptions()} />;
+        </React.Suspense>
+    );
  }
}

export default PieChart;

The two main changes in the above code is

  • React.lazy to load the dynamic import of Chart.jsx
  • React.Suspense to load and render the component. while it loads library it will show the <loader/> given in fallback option.

Lets see the different in the bundle sizes and how they gets loaded.

bundle with lazy load

In the network tab when we are in Home route it loads only initial bundle.

network (initial) with lazy load

And then we me navigates to Chart route it loads the highcharts the heavy library.

network with lazy load

lazy load chart

See this in action on now.sh and code is on github.

If you find my work helpful, You can buy me a coffee.