Write conditional code in the SharePoint Framework

Did you know, that when building SharePoint Framework solutions you can easily include code that will be available only in the debug build?

Conditional code in c#

When writing c# code, you might have used the #if compiler directive. Combined with the DEBUG symbol, it allowed you to write code, that would be compiled into the binary only in debug builds. While the #if directive could be used for all kinds of purposes, often developers used it to include additional debugging information or measure code's performance.

SharePoint client-side solutions

Where in the past we'd build SharePoint solutions using .NET, the recently released SharePoint Framework (SPFx) is the first SharePoint development model that focuses on client-side code. Using the SharePoint Framework, developers write code in TypeScript and the SPFx toolchain compiles it to JavaScript that runs in the web browser.

As the SharePoint Framework matures, developers will start building more and more complex solutions. Very likely, they would also like to include additional code in their debug builds to verify that everything is working as expected. And while JavaScript itself doesn't cater for that need, the SharePoint Framework does. Here is how.

Conditional code in the SharePoint Framework

SharePoint Framework uses a rich build toolchain. One of the tools used in SPFx is Webpack, which is responsible for combining the different source files together into one bundle and optimizing its contents.

Webpack's capabilities can be extended by plugins and one of them is the DefinePlugin. Using the DefinePlugin, developers can specify compilation constants which can be then used by Webpack to decide which lines of code should be included in the generated bundle.

Consider the following scenario: you want to measure the duration of the rendering of your SharePoint Framework client-side web part. In the render method you call the console.time and console.timeEnd functions.

// ...
export default class HelloWorldWebPart extends BaseClientSideWebPart<IHelloWorldWebPartProps> {
  public render(): void {
    console.time('HelloWorld_render');

    this.domElement.innerHTML = `
      <div class="${styles.helloWorld}">
        <div class="${styles.container}">
          <div class="ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}">
            <div class="ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1">
              <span class="ms-font-xl ms-fontColor-white">Welcome to SharePoint!</span>
              <p class="ms-font-l ms-fontColor-white">Customize SharePoint experiences using Web Parts.</p>
              <p class="ms-font-l ms-fontColor-white">${escape(this.properties.description)}</p>
              <a href="https://aka.ms/spfx" class="${styles.button}">
                <span class="${styles.label}">Learn more</span>
              </a>
            </div>
          </div>
        </div>
      </div>`;

    console.timeEnd('HelloWorld_render');
  }
  // ...
}

After adding your web part to the canvas, you can see the duration of the rendering in the console.

Duration of the rendering of a SharePoint Framework client-side web part displayed in the developer tools console

In the above code snippet, the code measuring the duration of rendering would be included in both the debug and the release build. But using the Webpack's DefinePlugin available as a part of the SharePoint Framwework build toolchain, you can change the code as follows to include the timing information only in debug builds:

// ...
export default class HelloWorldWebPart extends BaseClientSideWebPart<IHelloWorldWebPartProps> {
  public render(): void {
    if (DEBUG) {
      console.time('HelloWorld_render');
    }

    this.domElement.innerHTML = `
      <div class="${styles.helloWorld}">
        <div class="${styles.container}">
          <div class="ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}">
            <div class="ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1">
              <span class="ms-font-xl ms-fontColor-white">Welcome to SharePoint!</span>
              <p class="ms-font-l ms-fontColor-white">Customize SharePoint experiences using Web Parts.</p>
              <p class="ms-font-l ms-fontColor-white">${escape(this.properties.description)}</p>
              <a href="https://aka.ms/spfx" class="${styles.button}">
                <span class="${styles.label}">Learn more</span>
              </a>
            </div>
          </div>
        </div>
      </div>`;

    if (DEBUG) {
      console.timeEnd('HelloWorld_render');
    }
  }
  // ...
}

DEBUG is one of the constants specified in the configuration of the DefaultPlugin used in the SharePoint Framework. For debug builds it's set to true. For release builds it's set to false. When processing the source code, Webpack replaces the DEBUG constant with its value, changing the code to:

if (true) {
    console.time('HelloWorld_render');
}

In a release build the code would evaluate to:

if (false) {
    console.time('HelloWorld_render');
}

However, as the release build is optimized, Webpack simply removes the code from the generated release as it would never be executed anyway.

If needed, you could negate the value of the DEBUG constant, to include specific code in the release bundle only:

// ...
export default class HelloWorldWebPart extends BaseClientSideWebPart<IHelloWorldWebPartProps> {
  public render(): void {
    if (!DEBUG) {
      // this code goes into production code only
    }

    this.domElement.innerHTML = `
      <div class="${styles.helloWorld}">
        <div class="${styles.container}">
          <div class="ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}">
            <div class="ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1">
              <span class="ms-font-xl ms-fontColor-white">Welcome to SharePoint!</span>
              <p class="ms-font-l ms-fontColor-white">Customize SharePoint experiences using Web Parts.</p>
              <p class="ms-font-l ms-fontColor-white">${escape(this.properties.description)}</p>
              <a href="https://aka.ms/spfx" class="${styles.button}">
                <span class="${styles.label}">Learn more</span>
              </a>
            </div>
          </div>
        </div>
      </div>`;
  }
  // ...
}

Other SharePoint Framework compilation constants

DEBUG is one of the few compilation constants available in SharePoint Framework projects. Other constants that are defined are:

  • UNIT_TEST - which is set to true when running unit tests. You could use it to specify different configuration for unit testing your code
  • NPM_BUILD - used internally. Set to false unless the --npm argument is specified when building the project
  • BUILD_NUMBER - used internally for telemetry. Retrieved from the BUILD_BUILDNUMBER environment variable if specified. Otherwise set to the version specified in the component manifest

Defining custom compilation constants

Using custom compilation constants allows you to alter the compiled code without changing its source. One common scenario you could think of, is to use build arguments to specify a different API URL for development, UAT and production.

To define a custom compilation constant, change the Webpack configuration in gulpfile.js as follows:

'use strict';

const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');

build.configureWebpack.mergeConfig({ 
  additionalConfiguration: (generatedConfiguration) => {
    for (var i = 0; i < generatedConfiguration.plugins.length; i++) {
        var plugin = generatedConfiguration.plugins[i];

        if (!plugin.definitions) {
            // not DefinePlugin
            continue;
        }

        // define custom constant
        // here you could conditionally set different values
        // based on the specified gulp task arguments
        plugin.definitions.API_URL = JSON.stringify('https://preprod.contoso.com');

        generatedConfiguration.plugins[i] = plugin;

        break;
    }

    return generatedConfiguration; 
  } 
});

build.initialize(gulp);

Then, you could refer to the custom constant in code using:

// ...

declare var API_URL: boolean;

export default class HelloWorldWebPart extends BaseClientSideWebPart<IHelloWorldWebPartProps> {
  public render(): void {
    const apiUrl: string = `${API_URL}/api/`;
    
    // ...
  }
  // ...
}

Which in the generated bundle would be equal to:

// ...
var HelloWorldWebPart = (function (_super) {
    __extends(HelloWorldWebPart, _super);
    function HelloWorldWebPart() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    HelloWorldWebPart.prototype.render = function () {
        var apiUrl = 'https://preprod.contoso.com/api/';

        // ...
    }
}
// ...

Note the declare var API_URL: boolean; line above the class definition. Without this line, TypeScript will throw an error stating a reference to an unknown object API_URL.

Summary

Using compilation constants allows you to alter the compiled code without changing its source. SharePoint Framework uses the Webpack's DefinePlugin to support compilation constants. You can use the standard DEBUG constant to control which code should be included in debug and production builds. Additionally, you can define your own compilation constants to, for example define different API URLs to use during development, UAT and in production.

Comments

comments powered by Disqus