Wednesday, August 9, 2017

How to reduce the bundle size when using Office UI Fabric with React in SharePoint Framework

image

Yesterday I wrote about how the build process has changed for SharePoint framework web parts using the Office UI Fabric components and Fabric Core CSS. Previously you could take a dependency on whatever version was deployed in your tenant and your web parts were quite small in size as Office UI Fabric was pre-loaded from a Microsoft CDN. This has now changed, where the build process will bundle in the Office UI Fabric components to ensure your part works regardless of what Microsoft updates.

The caveat of this is that your web part grows in size (considerably), and if you have more than one web part on the page, all using Office UI Fabric components – well, load times do go up as code is loaded multiple times.

There’s a perfectly good reason for this change. Microsoft might decide to upgrade components they use, which could break your solutions. This means that you as a developer must control what version of Office UI Fabric you are using.

More: Read about it at https://dev.office.com/sharepoint/docs/spfx/web-parts/guidance/office-ui-fabric-integration

One solution to get the bundle size down again and at the same time have full control is to put Office UI Fabric components and CSS on a CDN, and having all parts re-use this. Fortunately I have done this for you :)

Step 1 – Reference Office UI Fabric components via CDN

First you need to add an explicit reference to the Office UI Fabric components you want to CDN in packages.json. I' am hosting v2.34.2 on my dev tenant, which you can use freely.

https://publiccdn.sharepointonline.com/techmikael.sharepoint.com/11510075fe4212d19d3e6d07c91981263dd697bf111cb1e5f0efb15de0ec08b382cde399/2.34.2/office-ui-fabric-react.bundle.min.js

npm i office-ui-fabric-react@2.34.2 --save
Next open up config/config.json and add a reference to the CDN in the externals section. This ensures that the build process will not include office-ui-fabric-react when you reference it in your components.
{
  "entries": [
    {
      "entry": "./lib/webparts/reactUiFabricBundling/ReactUiFabricBundlingWebPart.js",
      "manifest": "./src/webparts/reactUiFabricBundling/ReactUiFabricBundlingWebPart.manifest.json",
      "outputPath": "./dist/react-ui-fabric-bundling.bundle.js"
    }
  ],
  "externals": {
    "office-ui-fabric-react": "https://publiccdn.sharepointonline.com/techmikael.sharepoint.com/11510075fe4212d19d3e6d07c91981263dd697bf111cb1e5f0efb15de0ec08b382cde399/2.34.2/office-ui-fabric-react.bundle.min.js"
  },
  "localizedResources": {
    "reactUiFabricBundlingStrings": "webparts/reactUiFabricBundling/loc/{locale}.js"
  }
}

Step 2

When adding Office UI Fabric components to your web part, it’s now important to use dynamic referencing, and not static which I wrote about in the previous post. If you use static referencing, the component will be bundled into your web part instead of using the CDN version. This sort of goes against best practice, but is needed when accessing via CDN.

Correct

import { DefaultButton } from 'office-ui-fabric-react';

Wrong

import { DefaultButton } from 'office-ui-fabric-react/lib/Button';

Step 3

Next up is taking control of the Fabric CSS, using a custom prefix for the classes. As the default CSS has default theme colors in it, we also want to take advantage of theme tokens supported in SharePoint Framework (see post by Stefen Bauer).

I’m hosting v5.0.1 of the core CSS over at

https://publiccdn.sharepointonline.com/techmikael.sharepoint.com/11510075fe4212d19d3e6d07c91981263dd697bf111cb1e5f0efb15de0ec08b382cde399/5.0.1/office-ui-fabric.min.css which uses a pzl- prefix instead of ms- for the class names.

At the top of your component add the code below which will load the CSS asynchronously using fetch, and then processing it to handle the theme tokens to ensure proper theme colors. The code sets a global variable on the window object to signal if Fabric CSS is loaded or not, to prevent multiple web parts from re-loading and processing the CSS.

Note: If you are piggybacking on existing ms- class names for colors, you will notice that the colors are based on the tenant theme colors. By using the code in this post, the colors will be based on the theme colors of the SharePoint site.

import * as React from 'react';
import styles from './ReactUiFabricBundling.module.scss';
import { IReactUiFabricBundlingProps } from './IReactUiFabricBundlingProps';
import { DefaultButton, PrimaryButton } from 'office-ui-fabric-react';
import { loadStyles } from '@microsoft/load-themed-styles';

export default class ReactUiFabricBundling extends React.Component<IReactUiFabricBundlingProps, void> {
  // Added constructor to load and process CSS async
  constructor() {
    super();
    this.loadCss();
  }

  public async loadCss() {
    // Check if custom Fabric CSS is loaded
    if (window["UIFabricLoaded"]) {
      return;
    }
    window["UIFabricLoaded"] = true;

    // Load Fabric CSS from CDN
    let fabricCSSUrl = 'https://publiccdn.sharepointonline.com/techmikael.sharepoint.com/11510075fe4212d19d3e6d07c91981263dd697bf111cb1e5f0efb15de0ec08b382cde399/5.0.1/office-ui-fabric.min.css';
    const response = await fetch(fabricCSSUrl, { mode: 'cors' });
    if (response.ok) {
      response.text().then((data: string) => {
        // Process theme tokens
        loadStyles(data);
        // Reload webpart
        this.forceUpdate();
      });
    }
  }

  public render(): React.ReactElement<IReactUiFabricBundlingProps> {
    // Don't render until CSS is loaded
    if (!window["UIFabricLoaded"]) {
      return <span></span>;
    }

    return (
      <div className={styles.reactUiFabricBundling}>
        <div className={styles.container}>
          {/* Site theme */}
          <div className={`pzl-Grid-row pzl-bgColor-themeDarkAlt pzl-fontColor-white ${styles.row}`}>
            <div className="pzl-Grid-col pzl-u-lg10 pzl-u-xl8 pzl-u-xlPush2 pzl-u-lgPush1">
              <DefaultButton text="This block is Site themed" />
            </div>
          </div>
          {/* Tenant theme */}
          <div className={`ms-Grid-row ms-bgColor-themeDarkAlt ms-fontColor-white ${styles.row}`}>
            <div className="ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1">
              <DefaultButton text="This block is Tenant themed" />
            </div>
          </div>
        </div>
      </div>
    );
  }
}

If you test this in a tenant you might see something like this. The top block get’s the background color from the site theme, while the bottom is getting it from the tenant.

image

image

Summary

When building SharePoint Framework web parts using React and the Office UI Fabric, you should externalize the Fabric components as well as the Fabric CSS, so that multiple parts on page can re-use the code and you lower your page load times.

This certainly poses some questions

  • do we care about the resulting bundle sizes?
  • is React and Office UI Fabric react components the best approach for SharePoint Framework web parts?
  • how can I as a developer get the best resulting solution without plumbing hassle?

You can download the full sample code from https://github.com/wobba/spfx4fun/tree/master/react-bundle-ui-fabric