Bundle splitting using react-loadable

branch-name:  
Click To Copy

 

So far we created one bundle that loads the code for all components, no matter if we are going to render them or not. if for example  we just open one page with only one component we don’t need to load the code for all other components that are not on the page. It makes the load process slower, especially with a big site with many components.

Let’s fix this and split the bundle into chunks that contain the code only for the components that are on the page that the user opened, and load dynamically other components when the user navigates to a different sections in the site.

Install react-loadable

This is as simple as adding ‘react-loadable’ component.

yarn add react-loadable

Implementing react-loadabe

Before modifying PageLayout to use react-loadable, let’s create helper component called Loading that will show a spinning bar while the components are loading.

Create ‘Loading’ helper component

create a loading component folder mkdir ./src/components/Loading with index file with these contents:

./src/components/Loading/index.js

import React from 'react';

const styles = require('./styles.scss');

/**
 * HELPER COMPONENT TO DISPLAY LOADING ... AND HANDLE ERRORS WHEN COMPONENTS LOADS DYNAMICALLY 
 * @param {} props 
 */
const Loading = (props) => {
  if (props.error) {
    return (<div className={styles.wrapper}>
              <h3>Error loading component!</h3> 
              <p><b>{props.error.message}</b></p>
              <p>{props.error.stack}</p>
              <div><button onClick={ props.retry }>Retry</button></div>
            </div>);
  } else {
    return <div>Loading...</div>;
  }
} 

export default Loading;

and the styling file:

./src/components/Loading/styles.scss

.wrapper {
  background:rgb(141, 141, 172);
  padding-bottom: 10px;
  h3 {
    background: red;
    text-align: center;
  }
  div{
    text-align: center;
  }
}

 

and replace <div>Loading…</div> with the new component.

./src/containers/PageLayout/index.js

import React, { Component } from 'react';
import ComponentList from './ComponentList';
import Loading from '../../components/Loading';
import query from './query';
import { graphql } from 'react-apollo';

const styles = require('./styles.scss');

class PageLayout extends Component {

    constructor(props) {
      super(props);      
    } 
  
    render() {
      if(!this.props.data.getPageByUrl) {
        return (<Loading />);
      }  

...

Adding react-loadable.

`react-loadable’ works the same way like ‘import’ but will load required components on demand and won’t pre-load the components until it’s time to render them.

./src/containers/PageLayout/ComponentList/index.js

import Loadable from 'react-loadable';
import Loading from '../../../components/Loading';

/* Components */

const Header = Loadable({
  loader: () => import ('../../../components/Header'),
  loading: Loading
});

const Home = Loadable({
  loader: () => import ('../../../components/Home'),
  loading: Loading
});

const About = Loadable({
  loader: () => import ('../../../components/About'),
  loading: Loading
});

const Greetings = Loadable({
  loader: () => import ('../../../components/Greetings'),
  loading: Loading
});

const DogsCatalog = Loadable({
  loader: () => import ('../../../containers/DogsCatalog'),
  loading: Loading
});

export default {
  Home: Home,
  About: About,
  Greetings: Greetings,
  DogsCatalogWithRedux: Gallery,
  Header: Header
}

if you run the code you will notice that it will fail with this error message

`… Support for the experimental syntax ‘dynamicImport’ isn’t currently enabled (7:17)`

This is happening since Babel doesn’t have the appropriate plug-in to transpile dynamic imports. Let’s fix this by installing ‘plugin-syntax-dynamic-import’.

yarn add @babel/plugin-syntax-dynamic-import --dev

then add the plug-in into .babelrc file (line 6)

{
    "presets": [
      "@babel/preset-env",
      "@babel/preset-react"
    ],
    "plugins": ["@babel/plugin-syntax-dynamic-import"]   
  }

Now open the dev console, switch to the net tab and filter by .js and navigate to http://localhost:8080/home You will notice that there will be three bundles loaded: main-bundle.js, dist0-bundle.js and dist1-bundle.js the numbers could very but don’t worry about this as long as you see 3 bundles loaded.These bundles have the code for each component: Header component and the Home compoent. Navigate to the “about” section and you will see another bundle loading: dist2-bundle.js.

Now you have bundle split and loaded whenever the component is about to render. If you navigate back, you will see that the bundle won’t be reloaded.

Now the bundle splitting is done.

Fixing source mapping.

Let’s put debugger breakpoint into DogsComponent and take a look at the file source:

./src/containers/DogsCatalog/index.js

/* eslint-disable no-debugger */
import React, { Component } from 'react';
import { graphql } from 'react-apollo';
import DogDetails from './DogDetails';
import query from './query';
const styles = require('./styles.scss');
class DogsCatalog extends Component {
  constructor(props) {
    super(props);
    this.state = {
        breed: "labrador"
    }
  }
  handleClick(breedType) {
    debugger;
    this.setState({
        breed: breedType
    });
  }
...

 

Give it a try and check the location and you will see something like this:http://localhost:8080/dist0-bundle.js which is not quite understandable.

Let’s fix this. Open base Webpack file, and replace `devtool:'source-map' with devtool: 'cheap-module-eval-source-map' in order to have source mapping working after bundle splitting.

If we want even better source mapping with preserved column # as well we could use devtool: eval-source-map which will give the most detailed information for debugging but will make the building process slower.

There are a good amount of Dev tool config parameters, some are suitable only for development since they make bundle rebuild faster some for production only.

[table id=2 /]

For detailed information about all options visit WebPack dev tool page.

Make sure that the debugger statement is removed before we continue to the next chapter.

 

branch-name:  
Click To Copy

 

Leave a Reply