Asynchronous ReactJS Component loading with Webpack

To speed up the initial loading time of your ReactJS application, one efficient way is to load big components not when the application starts, but right before they are used.

Webpack (if you're not using it, give it a try) allows us to split our built application in chunks that'll be downloaded by the client when needed.

To be able to use that feature of Webpack in a ReactJS application, we have to find a way to package components in such chunks, and then to load them in an asynchronous manner.

Packaging components in separate chunks

To define a new (anonymous) chunk, just use the require.ensure() method Webpack provides, rather than the synchronous require(), like so (the code is formated using es6 syntax):

require.ensure([], () => {  
    require('pulpy/dist/styles/styles.scss');
    let pulpy = require('pulpy');
    // pulpy is now loaded by Webpack
    // we may use it synchronously
});

The require.ensure is core webpack, and tells webpack "When statically analyzing this scope (the anonymous function), please, make a separate chunk out of every deps you might stumble upon".

Then, when this code is executed in your application, webpack automatically downloads the corresponding chunk, and when done, executes the given anonymous function, with the garantee that calls to require() within this function are satisfied synchronously (as they always are).

Making chunks constants

When using the same asynchronously-loaded component in several interfaces of your application, if you define several times this require.ensure() block, several chunks will be built by webpack, and components will be downloaded several times in multiple different chunks, wasting our user's time and bandwitdh. Clearly, not what we want.

The solution is to put the require.ensure() block for this component in a loader function that'll be called multiple times throughout your application, effectively presenting only one occurrence of require.ensure() per component, resulting in one chunk built per component (good).

Doing so, we also take the opportunity to wrap the loader in a Promise, to make it easier to use the loader later. Like so:

// Components/Chunks/Pulpy.js
'use strict';

export default () => {  
    return new Promise(resolve => {
        require.ensure([], () => {
            require('pulpy/dist/styles/styles.scss');

            resolve({
                pulpy: require('pulpy')
            });
        });
    });
};

Using asynchronously loaded components in your application

Now that we know how to load components in an asynchronous fashion, how do we use them in our application ?

We simply take advantage of the amazing reactivity of ReactJS components, by calling our asynchronous loader in componentWillMount(), and then setting the loaded components in the state of our host component when available.

import PulpyLoader from 'Components/Chunks/Pulpy';

class MyComponent {  
  componentWillMount() {
    PulpyLoader().then(({ pulpy }) => {
      this.setState({ pulpy });
    });
  }

  render() {
    return (
      <div>
        {!(‘pulpy’ in this.state) && <p>Loading, please wait !</p>}
        {‘pulpy’ in this.state && <this.state.pulpy />}
      </div>
    );
  }
}

Once downloaded, Webpack won't download the chunk again, and the component will be available quite immediately.

What about isomorphic apps ?

I dont know. You tell me :)

Happy reacting !

comments powered by Disqus