Optimize SharePoint Framework Solutions

Overview

 
SharePoint Framework (SPFx) has become popular in a short span of time and has been widely adopted. SPFx is being developed using modern toolchain and client technologies, and helps to deliver the functionality faster. However, when we are using variety and a large number of web parts on a site, we always need to think of the overall performance and optimization techniques.
 

Common problems with solution development

  1. Poor architecture
  2. Less thinking about the impact on tenant
  3. Heavy packages bundling all assets together
  4. Referencing 3rd party libraries
  5. Developers interest
Due to one or more of the above points, optimization of SPFx solutions take a lower priority. Here are a few common observations and suggestions to optimize SPFx solutions.
 

Split multiple web parts to load individually

 
A single SPFx solution can have multiple web parts or extensions. All of these are typically referenced in config/config.json file as below.
  1. {  
  2.     "$schema""https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",  
  3.     "version""2.0",  
  4.     "bundles": {  
  5.         "my-spfx": {  
  6.             "components": [  
  7.                 {  
  8.                     "entrypoint""./lib/webparts/webpart1/webpart1.js",  
  9.                     "manifest""./src/webparts/webpart 1/webpart1.manifest.json"  
  10.                 },  
  11.                 {  
  12.                     "entrypoint""./lib/webparts/webpart2/webpart2.js",  
  13.                     "manifest""./src/webparts/webpart2/webpart2.manifest.json"  
  14.                 }  
  15.             ]  
  16.         }  
  17.     },  
  18.     "externals": {},  
  19.     "localizedResources": {}  
  20. }  
Both web parts get bundled in one single JavaScript file. This situation has the below points to consider.
  • When a single web part is added to a page, actually both are loaded.
  • If both the web parts are on one page, this will reduce the load time.
It is better to split both the web parts in separate files to reduce what is loaded on the page.
  1. {  
  2.     "$schema""https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",  
  3.     "version""2.0",  
  4.     "bundles": {  
  5.         "my-spfx-1": {  
  6.             "components": [  
  7.                 {  
  8.                     "entrypoint""./lib/webparts/webpart1/webpart1.js",  
  9.                     "manifest""./src/webparts/webpart1/webpart1.manifest.json"  
  10.                 }  
  11.             ]  
  12.         },  
  13.         "my-spfx-2": {  
  14.             "components": [  
  15.                 {  
  16.                     "entrypoint""./lib/webparts/webpart2/webpart2.js",  
  17.                     "manifest""./src/webparts/webpart2/webpart2.manifest.json"  
  18.                 }  
  19.             ]  
  20.         }  
  21.     },  
  22.     "externals": {},  
  23.     "localizedResources": {}  
  24. }  

Loading third party libraries dynamically

 
SharePoint Framework uses a webpack to bundle the solution. Webpack allows the dynamic import of part of an application. Consider an example of Moment library used in our SPFx web part to manage date operations.
 
Normal import
 
In the below example, Moment library will be included in the final package, irrespective of whether GetLongTime is called or not.
  1. // Import moment as a dependency  
  2. import * as moment from moment  
  3. .  
  4. .  
  5. export default class DateTimeClass {  
  6.     public GetLongTime(dateTime:string){  
  7.     return moment(dateTime).format("LLLL");  
  8.     }  
  9. }  
Dynamic import
 
In the below example, Moment library is loaded asynchronously only when the GetLongTime function is called. 
  1. export default class DateTimeClass {  
  2.     public async GetLongTime(dateTime:string){  
  3.          const moment = await import(  
  4.                 /* webpackChunkName: 'my-moment' */  
  5.                 'moment'  
  6.             );  
  7.         return moment(dateTime).format("LLLL");  
  8.     }  
  9. }  

Reference external libraries externally

 
We often use external libraries (e.g. jQuery) in our SPFx web part. Adding these external libraries as npm packages, and including them inside the final package, making it bulky. The external libraries can be referenced externally as below,
  1. Under the config folder, open the config.json file.
  2. To load jQuery from CDN location, add an entry to the externals section.
  1. {  
  2.   "entries": [  
  3.     {  
  4.       "entry""./lib/webparts/helloWorld/HelloWorldWebPart.js",  
  5.       "manifest""./src/webparts/helloWorld/HelloWorldWebPart.manifest.json",  
  6.       "outputPath""./dist/helloworld.bundle.js"  
  7.     }  
  8.   ],  
  9.   "externals": {  
  10.     "@microsoft/sp-client-base""node_modules/@microsoft/sp-client-base/dist/sp-client-base.js",  
  11.     "@microsoft/sp-client-preview""node_modules/@microsoft/sp-client-preview/dist/sp-client-preview.js",  
  12.     "@microsoft/sp-lodash-subset""node_modules/@microsoft/sp-lodash-subset/dist/sp-lodash-subset.js",  
  13.     "jquery""https://code.jquery.com/jquery-3.1.0.min.js"  
  14.   },  
  15.   "localizedResources": {  
  16.     "helloWorldStrings""webparts/helloWorld/loc/{locale}.js"  
  17.   }  
  18. }  
Import jQuery in SPFx web part as below,
  1. import * as $ from 'jquery';  

Analyzing your bundle

 
Extend the Webpack configuration in your SPFx project to understand the bundle.
 
Run the below command to install the webpack-bundle-analyzer package to your SPFx solution
  1. npm install webpack-bundle-analyzer --save-dev  
Update the contents of the gulpfile.js file as below,
  1. 'use strict';  
  2.   
  3. const gulp = require('gulp');  
  4. const path = require('path');  
  5. const build = require('@microsoft/sp-build-web');  
  6. const bundleAnalyzer = require('webpack-bundle-analyzer');  
  7.   
  8. build.configureWebpack.mergeConfig({  
  9.   additionalConfiguration: (generatedConfiguration) => {  
  10.     const lastDirName = path.basename(__dirname);  
  11.     const dropPath = path.join(__dirname, 'temp''stats');  
  12.     generatedConfiguration.plugins.push(new bundleAnalyzer.BundleAnalyzerPlugin({  
  13.       openAnalyzer: false,  
  14.       analyzerMode: 'static',  
  15.       reportFilename: path.join(dropPath, `${lastDirName}.stats.html`),  
  16.       generateStatsFile: true,  
  17.       statsFilename: path.join(dropPath, `${lastDirName}.stats.json`),  
  18.       logLevel: 'error'  
  19.     }));  
  20.   
  21.     return generatedConfiguration;  
  22.   }  
  23. });  
  24.   
  25. build.initialize(gulp);  
The "gulp bundle" command will generate a visualization file at ./temp/stats/[solution-name].stats.html file.
 

Summary

 
In this article, we explored the commonly observed behaviors and optimization techniques to build better solutions in SPFx. The performance of the SPFx web part can be optimized by following simple techniques. This will help to keep the bundle size within limits and improve the overall performance.