Migrate SharePoint JavaScript customizations to SharePoint Framework - reference functions from script files

When migrating existing SharePoint JavaScript customizations to SharePoint Framework you have to ensure that your script files can be used by client-side web parts. Here is how to do it.

Building SharePoint JavaScript customizations through script embedding

In the past you might have built a SharePoint customization using nothing else than JavaScript, HTML and maybe some CSS and images. Your customization would be embedded on the page using either the Content or the Script Editor Web Part and would contain the necessary HTML and references to its resources. With the recent release of the SharePoint Framework you wonder what the easiest way is to migrate your customization so that it can be used on modern team sites.

To illustrate the challenges and the migration process, let's use an overly simplified customization that displays a button that users can click to display a greeting.

Greeting message displayed by the greeting customization in SharePoint

The code of the customization is embedded on the page using the Script Editor Web Part.

Script Editor Web Part with code snippet displayed in SharePoint

The file with JavaScript is uploaded to a document library. The code itself defines the function used in the customization.

var greeting = function() {  
  alert('How are you doing?');
  return false;
}

Migrating SharePoint JavaScript customizations to SharePoint Framework

As your customization doesn't use any framework, you start with creating a new SharePoint Framework project that doesn't use any JavaScript framework either.

SharePoint Framework generator set to create a new project that doesn't use any JavaScript framework

Once the project is created, you start by adding the existing code file to the project without any further modifications.

Existing code file highlighted in Visual Studio Code

Then, you replace the HTML block in the web part's domElement with the HTML of your customization.

Customization HTML added to the web part's dom element

In order to load the contents of the code file, you add the require('./my-script.js') statement to the web part's render method.

Require statement in the web part to import customization's code

If you would try to run the web part using the gulp serve command at this moment, the customization wouldn't work as expected. Instead, after clicking the button, you would see an error similar to the following:

Uncaught ReferenceError: greeting is not defined(…)  

Reference error after clicking the button in the client-side web part

So why isn't it just working as it used to in the Script Editor Web Part?

A word about JavaScript modules

When you referenced script in a Script Editor Web Part that script was loaded in the global scope of the page. Every variable you defined was by default assigned to the top-level window object. While things just worked and you didn't have to worry about anything, there was the occasional risk of different elements on the page colliding due to using the same variable names.

Similarly to Script Editor Web Part, SharePoint Framework client-side web parts integrate with the page's DOM. To avoid collisions with other elements on the page, client-side web parts use JavaScript modules that encapsulate script contents and make them available only to the particular web part.

Although you included your script without any further modifications, it's not how it's loaded in the web part. You can see it by analyzing the contents of the generated bundle file in the project dist folder.

Original script included in the generated web part bundle

Your original script is now wrapped in something similar to:

/* 3 */
/***/ function(module, exports) {

    var greeting = function() {
      alert('How are you doing?');
      return false;
    }

/***/ }

Your previously globally-scoped function is now a local variable inside a function which is exactly what makes it inaccessible outside of this function. So how do you solve this?

Exposing script variables outside of the script file

When migrating your existing SharePoint customizations to the SharePoint Framework, ideally you would rewrite them to make the best use of the capabilities that SharePoint Framework has to offer. As you can imagine, it wouldn't however be a trivial task, depending on the complexity of your customization. There are a few other ways in which you can wrap your existing customizations into SharePoint Framework client-side web parts with considerably less effort.

Assigning variables to the global scope

Similarly to what you've done in the past, you could expose your variables on the global scope. Since your code is wrapped in a function, you have to do that explicitly by prepending your variable names with window.:

window.greeting = function() {  
  alert('How are you doing?');
  return false;
}

Mouse pointing to the script function logged in Chrome developer tools. Alert message with greeting displayed on the screen

This is a relatively simple tweak that makes your code work, exactly the same way as previously. The downside is, that once again you are at risk of colliding with other elements on the page, should they use the same variable names as you.

Exporting variables outside of the module

When you include a JavaScript file in your SharePoint Framework project, Webpack, responsible for bundling the different resources in the build process, generates a module for your script and includes it in the generated bundle. Since your file is built using plain JavaScript, it doesn't use the module construct. With a little addition to your script you can choose which pieces of your script should be exposed in the web part.

Consider the following change to your script:

var greeting = function() {  
  alert('How are you doing?');
  return false;
}

module.exports = {  
  greeting: greeting
};

To the end you add the module.exports statement with an object containing the different elements of your script that should be available outside of this script - think public members of a class.

Having defined your public members, you could change your web part to use the exported greeting function:

public render(): void {  
  this.domElement.innerHTML = `
    <input type="button" value="Click me"/>`;

  const myScript = require('./my-script.js');
  this.domElement.querySelector('input').addEventListener('click', myScript.greeting);
}

First of all, the require statement is changed to an assignment. That way you get the reference to all exported pieces of your script, which you need later on when defining the event handler for the button click event.

Next the click event handler, previously defined using the onclick attribute on the button, is removed. Instead, you retrieve the reference to the button using the querySelector function. Once you have it, you use the addEventListener function to assign the exported greeting function as a handler of the click event.

Mouse pointing to the undefined statement logged in Chrome developer tools. Alert message with greeting displayed on the screen

This approach is great because it doesn't require you to rewrite all your code to TypeScript and yet allows you to benefit of the modules construct used in SharePoint Framework client-side web parts. By following this pattern you keep your code encapsulated inside the web part and have no risk of colliding with other elements on the page.

Summary

When migrating SharePoint customizations built with Content or Script Editor Web Parts to SharePoint Framework there are a number of things you have to keep in mind. One of them is how the SharePoint Framework deals with script files included in the web part and how it encapsulates them inside the generated bundle. With a little change to the script code you can use the existing script files with SharePoint Framework without having to completely rewrite them.

Comments

comments powered by Disqus