The easiest way to reference external JavaScript frameworks in SharePoint Framework Client-Side Web Parts is by bundling them together with the Web Part. But is it really a good idea?
SharePoint Framework is a new model for building SharePoint customizations. In contrary to other SharePoint development models available to date, SharePoint Framework is based 100% on JavaScript and encourages developers to leverage existing JavaScript frameworks to build powerful solutions. The idea behind the SharePoint Framework isn’t revolutionary. For years developers have been adding scripts to SharePoint pages using the Content Editor- or the Script Editor Web Part. For the first time however, SharePoint offers us a standardized way of building client-side solutions and integrating them with SharePoint.
SharePoint Framework provides developers with a build toolchain based on open-source tooling such as Gulp and Webpack. When building SharePoint Framework projects, these tools automatically take care of referenced resources and package them together with Web Part’s code in the process called bundling. While this default behavior allows developers to focus on building their Web Part rather than managing dependencies, it produces output you might consider undesirable. Here is why.
To illustrate why bundling external JavaScript frameworks in SharePoint Framework let’s build a simple Web Part using Angular and styled using ngOfficeUIFabric.
Let’s start by creating the Web Part. Since the SharePoint Framework Yeoman generator doesn’t support scaffolding Angular Web Parts, we will use the ‘no JavaScript framework’ Web Part and add Angular to it ourselves.
Next, let’s install Angular and ngOfficeUIFabric into our project by running in the command line:
$ npm i angular ng-office-ui-fabric -S
To use Angular in our Web Part we also need Angular typings for TypeScript which we install using:
$ tsd install angular --save
Finally, let’s reference both frameworks in our Web Part:
import { BaseClientSideWebPart, IPropertyPaneSettings, IWebPartContext, PropertyPaneTextField } from '@microsoft/sp-client-preview'; import styles from './HelloWorld.module.scss'; import * as strings from 'helloWorldStrings'; import { IHelloWorldWebPartProps } from './IHelloWorldWebPartProps'; import * as angular from 'angular'; import 'ng-office-ui-fabric'; export default class HelloWorldWebPart extends BaseClientSideWebPart<IHelloWorldWebPartProps> { public constructor(context: IWebPartContext) { super(context); } public render(): void { this.domElement.innerHTML = ` <div class="${styles.helloWorld}"> <!-- omitted for brevity --> </div>`; angular.module('helloworld', []); angular.bootstrap(this.domElement, ['helloworld']); } // omitted for brevity }
At this point it doesn’t really matter that the Angular code doesn’t do anything. What matters is what we do next.
Let’s do a release build of our Web Part by running:
$ gulp bundle --ship
If you now look into the ./temp/deploy folder you will see three files: your Web Part’s manifest, the main bundle and a file with localization strings.
Notice the size of the main bundle: 336KB.
If look into the contents of the generated bundle you can see that, next to your Web Part’s code, it contains Angular (line 2) and ngOfficeUIFabric (line 12).
Now, let’s add another Web Part to our project by calling:
$ yo @microsoft/sharepoint
Because Angular and ngOfficeUIFabric are already installed in our project, and we already have Angular typings for TypeScript, we can proceed with referencing Angular and ngOfficeUIFabric in the newly added Web Part just as we did previously.
Once again, let’s execute a release build by running:
$ gulp bundle --ship
Without surprise, if you look at the contents of the ./temp/deploy folder you will see two bundles, one for each Web Part, each one of them being 336KB in size and including Angular and ngOfficeUIFabric by itself.
If you would add both Web Parts to a page, users would be essentially downloading Angular and ngOfficeUIFabric twice – once with each Web Part. What if you would consider this approach in the context of a complete intranet with many sites, pages and Web Parts? How inefficient would that be?
Luckily there is a better way to handle external frameworks when building Client-Side Web Parts on the SharePoint Framework, and it doesn’t even require you to change anything about your code.
You have just seen how bundling external frameworks with your Web Parts built on the SharePoint Framework bloats the size of the generated bundles and makes users spend unnecessary time waiting for the Web Parts and their dependencies to load. What if we could change how the SharePoint Framework handles external JavaScript frameworks that we are using in our Web Parts? What if these frameworks could be downloaded once for all Web Parts on the page and maybe even once for the whole intranet? Achieving this is easier than you think and it doesn’t even require you to rewrite any of your code.
In the previous example we saw how the SharePoint Framework bundles Angular and ngOfficeUIFabric together with our Web Part’s code. When working with the SharePoint Framework we can however choose whether we want to have our dependencies bundled together with Web Parts or not.
In order to load external JavaScript frameworks from a URL instead of bundling them together with the Web Part, we have to specify them as an external module in the config/config.json file’s externals section. When using Angular and ngOfficeUIFabric you would extend your configuration like this:
{ "entries": [ { "entry": "./lib/webparts/helloWorld/HelloWorldWebPart.js", "manifest": "./src/webparts/helloWorld/HelloWorldWebPart.manifest.json", "outputPath": "./dist/hello-world.bundle.js" }, { "entry": "./lib/webparts/helloWorld2/HelloWorld2WebPart.js", "manifest": "./src/webparts/helloWorld2/HelloWorld2WebPart.manifest.json", "outputPath": "./dist/hello-world-2.bundle.js" } ], "externals": { "@microsoft/sp-client-base": "node_modules/@microsoft/sp-client-base/dist/sp-client-base.js", "@microsoft/sp-client-preview": "node_modules/@microsoft/sp-client-preview/dist/sp-client-preview.js", "@microsoft/sp-lodash-subset": "node_modules/@microsoft/sp-lodash-subset/dist/sp-lodash-subset.js", "office-ui-fabric-react": "node_modules/office-ui-fabric-react/dist/office-ui-fabric-react.js", "react": "node_modules/react/dist/react.min.js", "react-dom": "node_modules/react-dom/dist/react-dom.min.js", "react-dom/server": "node_modules/react-dom/dist/react-dom-server.min.js", "angular": { "path": "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js", "globalName": "angular" }, "ng-office-ui-fabric": "https://cdnjs.cloudflare.com/ajax/libs/ngOfficeUiFabric/0.12.3/ngOfficeUiFabric.js" }, "localizedResources": { "helloWorldStrings": "webparts/helloWorld/loc/{locale}.js", "helloWorld2Strings": "webparts/helloWorld2/loc/{locale}.js" } }
In the example above we specify that we want to load Angular and ngOfficeUIFabric from an external location. Please note how the names we used (angular and ng-office-ui-fabric) match the names we used in theimport statements in our Web Parts.
If you look closely at the configuration, you might notice that the way we define Angular and ngOfficeUIFabric is different. While for ngOfficeUIFabric we only specify its URL, for Angular we specify its URL and global name.
Because ngOfficeUIFabric is an UMD module, it will be correctly registered with the SharePoint Framework all by itself without any effort on our side. Angular on the other hand, is a non-AMD script that registers globally. When loading non-AMD scripts we have to instruct SharePoint Framework how to load and register them correctly or we will see errors when trying to add our Web Part to a page.
In the exampe above we load our external JavaScript frameworks from CDN. Using a CDN isn’t required though and you might as well use a hosting location owned by your organization or even a SharePoint Library. As long as users can access the scripts when working with Web Parts everything will work as expected.
Having extended our project configuration with the hosting locations for Angular and ngOfficeUIFabric, let’s clean up previous builds and once again run a production build using:
$ gulp nuke $ gulp bundle --ship
If you take a look at the contents of the ./temp/deploy folder now, you will see two Web Part bundles, 6KB each, which is quite a difference than the 336KB we seen earlier!
Our Web Parts still need Angular and ngOfficeUIFabric though, so it wouldn’t be fair to say that we just saved 330KB on each Web Part. In the current setup however, both Angular and ngOfficeUIFabric will be downloaded once for all Web Parts when the user visits the page:
Also notice how, on a subsequent request, both Angular and ngOfficeUIFabric are loaded from the browser cache.
If your organization would standardize the different JavaScripts frameworks used throughout the intranet, users would need to download the specific frameworks only once and for the foreseeable future they could reuse the locally stored copies, which would significantly improve the intranet’s performance.
Based on what you’ve seen, you might wonder what the point of bundling is. If it increases the size of Web Part bundles that much, is there any point of using it at all or should you avoid bundling external frameworks at all costs?
Despite its impact on bandwidth, bundling external frameworks with SharePoint Framework Web Parts makes sense in some scenarios. For example, if your organization has a standard solution, that should work on any intranet and you can’t rely on which frameworks are already available on the page, bundling is an easy way for you to ensure that everything that’s required for your Web Part to work as expected is available. Some organizations don’t allow loading scripts from public CDNs and providing them together with your Web Part is one way to work around that limitation. Also, if your Web Part requires a number of smaller scripts, it’s better from the performance point of view to combine them together into a single bundle than to load the different script files separately. If that collection of scripts is used by multiple Web Parts then it would make sense to store it separately and reference it as external script.
By default, external frameworks referenced in SharePoint Framework Client-Side Web Parts are bundled together with the particular Web Part which unnecessarily bloats the size of the generated bundle. While bundling makes sense in specific scenarios, in most cases, when building intranets, it would be beneficial for the performance to load JavaScript frameworks from external locations such as public CDNs or a hosting location owned by your organization.