Article
7 comments

Optimize your SPFx projects with React.lazy

SharePoint Framework offers you a pretty solid project setup, but on the other hand, it doesn’t give you options to optimise the gulp, build or the webpack configuration.
The more web parts exist in a single project, the slower the build task become and all the code in the project will be compiled at once, instead of smaller incremental builds. Technical possible but not yet supported.

Optimise your SPFx project with React.lazy

The good thing is that there are some options you can directly trigger from your web part code. Some of these options affect webpack others can be applied in ReactJS. Not only in case of build times but also case of user experience and web part performance.

The Problem is Code Duplication

First, we should take a look at the root causes. Let’s assume you write a project with two web parts in it. In your project, there are some components that you like to reuse in other web parts too. Let’s say you have some specific UI components that you can reuse. Lucky you have the import statement and can import all from the same source location.

So Web Part 1 imports the following components:

import Component1 from '../../../common/Component1';
import Component2 from '../../../common/Component2';

You use those later in the render method.

public render(): React.ReactElement<ISomeProperty> {
  return (
    <div className={styles.rootComponent}>
      <Component1></Component1>
      <Component2></Component2>
    </div>
  );
}

Web Part 2 only requires ‘Component2’ so you import this component the same way on top.

import Component2 from '../../../common/Component2';

And again you use it in the render method.

public render(): React.ReactElement<ISomeProperty2> {
  return (
    <div className={styles.anotherComponent}>
      <Component2></Component2>
    </div>
  );
}

So ‘Component1’ and ‘Component2’ are both stored in a location outside of your regular web part code you might assume those files stay independent.

This assumption is, sadly, incorrect. The compiler includes in the first web part duplicate the ‘Component1’ and ‘Component2’ and compiles it directly into Webpart1. The second web part compiles the code of ‘Component2’ again and includes it into WebPart 2. This result is the same program code of ‘Component2’ exists in both web parts.

The result is that you load two times the same code on a page under the assumption you like to display both web parts on the same page. In such simple scenarios, it is not a problem but the more components you use and reuse the more code gets duplicated. Result in slower build, loading times and bigger single-file JavaScripts.

Even regular import merges the code of the external module, also if the initial load do not require them.
We are talking about some kilobytes added to the Javascript of every web part. It might not make such a big difference, but there is a smarter way in ReactJS.

React lazy loading and react-loadable

Wouldn’t it be nice to split out Component1 and Component2 into separate files? Maybe even load them only on-demand instead of always load them by default?

Well, this is possible in React since version 16.6. to load all required components for the first render and lazy load other components on-demand later.

The components remain in separate files. These files can other parts consume — others such as web parts. So it avoids the previously mentioned code duplication.

For older React version, the same mechanism is available through the react-loadable. An excellent package.

How to implement?

To use React lazy loading the code needs a bit adjusted. For web part two, we load the components instead of importing like this for the first web part.

import Component1 from '../../../common/Component1';
import Component2 from '../../../common/Component2';

The code for loading these components using React lazy loading looks like this.

const Component1 = React.lazy(() => import('../../../common/Component1'));
const Component2 = React.lazy(() => import('../../../common/Component2'));

What this code does it doesn’t return the component at first hand. Instead, it returns a promise that tells the user interface when the part is loaded and updates a placeholder.

Wait. A placeholder? Well, sort of the React Element that serves as a placeholder is named .
It directly handles the promise used in React.lazy and renders a fallback until the components are loaded.

public render(): React.ReactElement<ISomeProperty> {
  return (
    <React.Suspense fallback={<div>LOADING...</div>}>
      <Component1></Component1>
      <Component2></Component2>
    </React.Suspense>
  );
}

React detects if the component requested in the Suspense tag is already loaded. Otherwise, those component get downloaded from the server. Until this completes the user interface shows the ‘Loading…’ div.

During the build and in the final bundle both components remain in separate files. So what happens in here to be more specific. ‘Loading…’ gets shown until ‘Component1’, and ‘Component2’ fully loads via an HTTP request.

In general and dependant on how big your components are you shouldn’t see the loading at all. So nothing to worry about but a vast improvement in case of loading and code separation.

A look on the file system

Like I previously mentioned a regular web part compiles to single web part file, including all external files that got imported.

Default web part compilation

Default web part compilation and resulting files

It so not optimal in case of loading the web part and in case of WebPack. Webpack favours more and smaller files.

Decrease the total size of the compilation to increase build performance. Try to keep chunks small.
* Use fewer/smaller libraries.
* Use the SplitChunksPlugin in Multi-Page Applications.
* Use the SplitChunksPlugin in async mode in Multi-Page Applications.
* Remove unused code.
* Only compile the part of the code you are currently developing on.
Source: WebPack Build Performance

With Ready.lazy, this situation changes because ‘Component1’ and ‘Component2’ remain in separate files. For the two web parts and two components, the dist folder shown below.

Components and Webpart Code in seperate files

Components and Webpart Code in seperate files

The first try without lazy loading uses 163,73kb over six generated files. The ‘dist’ folder with the use of React.lazy reduce the overall size to 139,68Kb. So we saved about 24kb or 14%.

Sure the solution used is pretty small, but you will see more improvement the larger your projects get.

Solution without lazy loading:

Filename Filesize in Bytes Filesize in KB
43bfe44d-6e68-4d16-80c0-1ad60ce59db8.manifest.json 1782 1,74
f8d9a40e-7e79-41d6-b7f9-ba882337e69e.manifest.json 1794 1,75
hello-world-2-web-part.js 37883 37,00
hello-world-2-web-part.js.map 41904 40,92
hello-world-web-part.js 40742 39,79
hello-world-web-part.js.map 43559 42,54
Total 167664 163,73

Solution with React.lazy

Filename Filesize in Bytes Filesize in KB
0.0_86e92271cc4ecb422123.js 18318 17,89
0.0_86e92271cc4ecb422123.js.map 29092 28,41
1.1_ef46a63e8bbafe270fcb.js 7340 7,17
1.1_ef46a63e8bbafe270fcb.js.map 5545 5,42
2.2_28f3c257766c952741dc.js 7302 7,13
2.2_28f3c257766c952741dc.js.map 5502 5,37
43bfe44d-6e68-4d16-80c0-1ad60ce59db8.manifest.json 1782 1,74
f8d9a40e-7e79-41d6-b7f9-ba882337e69e.manifest.json 1794 1,75
hello-world-2-web-part.js 18927 18,48
hello-world-2-web-part.js.map 13511 13,19
hello-world-web-part.js 19615 19,16
hello-world-web-part.js.map 14302 13,97
Total 143030 139,68

Cool, but we use library components

I have some troubles with library components. I don’t see many use cases that library component solves and are unique to them in SPFx.
In fact, with React.lazy, you accomplished the same benefit like library components, but on a smarter scale, that supports even multiple versions.

I plan a more into depth article on further optimisations and what scenarios are for library components.

Verdict

Instead of writing a ReactJS web part, you should also consider when a component needs to be loaded and how you like to reuse your user interface components.
Reacts Lazy loading also allows you to go way beyond just user interface components. Webpack might be improved dramatically, but I haven’t extensively tested it yet, older project require some refactoring and time will tell.

With every development project planning is crucial, and to write a web part without know optimisation techniques can make a difference for your user.

This blog post only scratches the surface on how to optimise SPFx solutions. Other methods involve some other development techniques and knowledge of Webpack.

Further information:

7 Comments

    • Chunk id 0 is @microsoft/load-themed-styles/lib/index.js

      Because both web parts use CSS that might contain theme slots webpack is smart enough when ‘Component1’ and ‘Component2’ use the same classes/components to put them out into one unique chunk that has a higher priority to load. Chunk 0 is required for Chunk 1 and Chunk 2.

      That is, in this case, another topic because of it all relates to web pack and chunk naming. A blog post is coming soon.

      Reply

      • thanks for the quick answer! so you did an additional thing (like webpack chunks or so) to get #0, and that additional step is not documented here right now correct? You didn’t end up “magically” with a new chunk? Or is it because component 1 & 2 have a common dependency that webpack is creating automatically a third chunk? in that case why wasn’t it doing it before you implemented react lazy?

        Reply

        • No, it ended up to appear webpack “magically” the way web pack treat the chunking and source code. With additional settings in Webpack you can better control the naming of those chunks.

          So you can even define your own vendor bundles.

          Reply

    • Chunk id 0 is @microsoft/load-themed-styles/lib/index.js

      Because both web parts use CSS that might contain theme slots webpack is smart enough when ‘Component1’ and ‘Component2’ use the same classes/components to put them out into one unique chunk that has a higher priority to load. Chunk 0 is required for Chunk 1 and Chunk 2.

      That is, in this case, another topic because of it all relates to web pack and chunk naming. A blog post is coming soon.

      Reply

    • I haven’t specifically tested it own IE 11 but there could be some issue. The same thing not explicitly included in this blog post, because it is about the newest version is a fallback NPM package loadable-components. This has explicit support for IE11.

      Reply

Leave a Reply

Required fields are marked *.


This site uses Akismet to reduce spam. Learn how your comment data is processed.