When a SharePoint Framework based use-case arising that includes conditionally loading an external library such as jQuery, an option exists that includes looking to see if a library has already been loaded, and then conditionally loading additional libraries using SPComponentLoader.loadScript
SharePoint Framework Limitations
The common method of loading external libraries in SPFx solutions is to include any given library in the config/config.json file, in particular by including the module in the “externals” section. External libraries will then be loaded as modules by a given component (webpart or extension) when it is referenced. But there are situations when a given library such as jQuery has already been loaded on the page. In Modern SharePoint, this should not be a common concern as likely jQuery or any other library would be loaded as an external library within another SPFx component, and thus SPFx will help manage what has and has not been loaded.
There are scenarios where an external library may have already be loaded via unmanaged methods, I can think of two off hand although I am sure there are more. First, consider when including a SPFx webpart on a classic experience where the classic experience has custom branding that already loaded jQuery. A second more dangerous example is if another developer, please do not let this be you, created a hackish SPFx script editor webpart that loads up jQuery on its own rather than through the SPFx module loader process.
In any case, SPFx currently (as of 1.4.0) does not allow for conditional loading of modules that I am aware of, thus when the use case arises, I offer the following workaround.
Conditionally load an external library such as jQuery
I am going to make use of SPComponentLoader.loadScript for good or bad (check out a recent post by Waldek Maskykatz regarding issues with external libraries) and a little help with Typings.
In the following walk through, I am going load up jQuery if it hasn’t already been loaded by some other process, based on a helloworld example with no framework.
Add type definition for external library is available
Based on an untouched helloworld webpart, if there is a typing definition available, go ahead and install that. In this case I also include jquery as I want to pull JQueryStatic type later on.
1 | npm i jquery @types/jquery |
You should not have to do anything special to now utilize this type definition, so time to open up the webpart .ts file, i.e. /src/webparts/”your webpart”/”your webpart”WebPart.ts.
Initialize global variables and include additional modules
Since we might be loading additional scripts, we need to import SPComponentLoader found in the @microsoft/sp-loader module. We might also want to be able to Type a library we require, in our case jQuery, so we will import jQueryStatic from jquery
We will also want to make available what is, or will become, global variables to the browser. This can be done by “declaring” variables before our default class. We will make available “window“, “jQuery“, and “$” in our example so that we can access / verify window.jquery, then once jQuery is found, or loaded, we can access the global vars that jQuery creates, both “jQuery” and “$”, later in our code.
The combined code referrenced above looks like:
1 2 3 4 5 | import { SPComponentLoader } from '@microsoft/sp-loader'; import JQueryStatic = require('jquery'); declare var window : any; declare var jQuery : JQueryStatic; declare var $ : JQueryStatic; |
Check if a library exists, and if it does not, load it
The last bit of code will likely go in your init function or render function of your SPFx code, in our case again, the webpart .ts file. The stategy here is to check to see if the library exists and if it does not, then load it using SPComponentLoader.loadScript. This method returns a promise, thus we can execute additional code after the library has been loaded.
The following code shows the general process I have successfully used to verify and load if necessary jQuery in a SPFx webpart. I have stripped this down to the basics for easy reference and I also suggest placing this type of code in its own function that returns a promise to create a more modular approach.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | //Determine if jQuery is avaialble in window.jQuery if ('undefined' != typeof window.jQuery) { jQuery = window.jQuery as JQueryStatic as JQueryStatic; } if ('undefined' == typeof jQuery) { // jQuery not present console.log("jQuery not found, load jQuery"); //load up jQuery SPComponentLoader.loadScript('https://code.jquery.com/jquery-2.2.4.min.js', { globalExportsName: 'jQuery' }).catch((error) => { console.log("jQuery loader error occurred"); }).then(() => { console.log("jQuery loaded, 'jQuery' and '$' global vars are now available."); //TODO: Add your code here now that jQuery is available $("#jQueryLoader").html("jQuery has now been loaded"); }); } else { // jQuery present console.log("jQuery found and available"); //TODO: Add your code here if jQuery already available } |
Explanation
Thus the above code looks to see if jQuery exists within the global “window” variable that we can access in our TypeScript because we “declared” it earlier. If found, then assign the jQuery var to window.jQuery, although that is redundant. At this point we would also know that $ is available as well.
If window.jQuery is not available, then we know we need to load it. This is accomplished with SPComponentLoader.loadScript, and upon success, we now know that $ is available, thus we can use that as well.
Your library may be different but the priciples should be the same.
The complete WebPart TypeScript code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | import { Version } from '@microsoft/sp-core-library'; import { BaseClientSideWebPart, IPropertyPaneConfiguration, PropertyPaneTextField } from '@microsoft/sp-webpart-base'; import { escape } from '@microsoft/sp-lodash-subset'; import styles from './JQueryConditionalModuleWebPart.module.scss'; import * as strings from 'JQueryConditionalModuleWebPartStrings'; export interface IJQueryConditionalModuleWebPartProps { description: string; } import { SPComponentLoader } from '@microsoft/sp-loader'; import JQueryStatic = require('jquery'); declare var window : any; declare var jQuery : JQueryStatic; declare var $ : JQueryStatic; export default class JQueryConditionalModuleWebPart extends BaseClientSideWebPart<IJQueryConditionalModuleWebPartProps> { public render(): void { this.domElement.innerHTML = ` <div class="${ styles.jQueryConditionalModule }"> <div class="${ styles.container }"> <div class="${ styles.row }"> <div class="${ styles.column }"> <span class="${ styles.title }">Welcome to SharePoint!</span> <p class="${ styles.subTitle }">Customize SharePoint experiences using Web Parts.</p> <p class="${ styles.description }">${escape(this.properties.description)}</p> <a href="https://aka.ms/spfx" class="${ styles.button }"> <span class="${ styles.label }">Learn more</span> </a> <div id="jQueryLoader"></div> </div> </div> </div> </div>`; //Determine if jQuery is avaialble in window.jQuery if ('undefined' != typeof window.jQuery) { jQuery = window.jQuery as JQueryStatic as JQueryStatic; } if ('undefined' == typeof jQuery) { // jQuery not present console.log("jQuery not found, load jQuery"); //load up jQuery SPComponentLoader.loadScript('https://code.jquery.com/jquery-2.2.4.min.js', { globalExportsName: 'jQuery' }).catch((error) => { console.log("jQuery loader error occurred"); }).then(() => { console.log("jQuery loaded, 'jQuery' and '$' global vars are now available."); //TODO: Add your code here now that jQuery is available $("#jQueryLoader").html("jQuery has now been loaded"); }); } else { // jQuery present console.log("jQuery found and available"); //TODO: Add your code here if jQuery already available } } protected get dataVersion(): Version { return Version.parse('1.0'); } protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration { return { pages: [ { header: { description: strings.PropertyPaneDescription }, groups: [ { groupName: strings.BasicGroupName, groupFields: [ PropertyPaneTextField('description', { label: strings.DescriptionFieldLabel }) ] } ] } ] }; } } |
Next Steps
Ideally the above stragegy is not needed as we will all move to modern experiences and will ideally load external libraries in a manner that SPFx will help manage for us. In cases where that is not happening, consider using SPComponentLoader.loadScript. Do consider the security issues that Waldek brings up and other issues such as general best practices.
Credits to Waldek who helped me consider solutions and their ramifications to this problem.
Speak Your Mind