__aSyNcId_<_btXfSQEN__

Correctly reference images in SharePoint Framework solutions

When you try to reference images in your SharePoint Framework solution, all you see are broken images. With this one tweak, they will be working, just the way you expect them to.

Referencing images in the SharePoint Framework

When building client-side web parts using the SharePoint Framework, the odds are high, that at some point you will want to reference an image: a logo, an icon or decoration graphic to be shown in your web part.

There are two common ways to reference an image in SharePoint Framework client-side web parts:

  • using an <img src=""> tag, where the src attribute points to the URL of your image
  • using CSS, setting the image using background-image property and pass the URL of your image using the url() value

SharePoint Framework uses webpack to process and package solution's code. As a result, source code such as JavaScript or CSS becomes combined into a single bundle file called the web part bundle. Assets referenced in these files, such as fonts or images, are copied to the dist output directory. To avoid conflicts, in the build process referenced asset files are renamed and copied to the output folder. The references in code are then updated to point to the new file name.

To put it into practice, imagine a web part that would display a logo:

SharePoint Framework client-side web part showing Rencore logo

The logo file is a .png file, included in the web part source folder.

Logo file highlighted in the Visual Studio Code explorer pane

In the web part's code, this logo is referenced using a require statement:

// ...
const logo: any = require('./assets/rencore.png');

export default class HelloWorldWebPart extends BaseClientSideWebPart<IHelloWorldWebPartProps> {
  public render(): void {
    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>
        <img src="${logo}" alt="Rencore logo" width="150" style="margin: 10px 0 0 30px" />
      </div>`;
  }

  // ...
}

What's assigned to the logo constant, is the URL of the output image processed by webpack. Unfortunately, if you would add this web part to the local workbench, you would see an invalid image reference.

Invalid image reference in the client-side web part added to the local workbench

If you look at the URL of the referenced image, you will see, that the URL is relative to the page, rather to where the web part bundle is located, which is in https://localhost:4321/dist.

While you could manually change the URL, to point either to the dist directory either as a relative or a server-relative URL, it would break after deploying your web part in production. There, your web part is added to a page in SharePoint whereas the web part bundle and the image are hosted on another URL.

Often, similar issues are solved by referencing images through CSS and pointing to the image using a URL relative to the location of the CSS file, like:

.img {
    background: url('./assets/rencore.png');
}
// ...
import styles from './HelloWorld.module.scss';

export default class HelloWorldWebPart extends BaseClientSideWebPart<IHelloWorldWebPartProps> {

  public render(): void {
    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 class="${styles.img}" title="Rencore logo"></div>
      </div>`;
  }

  // ...
}

Unfortunately, because the SharePoint Framework uses webpack, all referenced CSS files are included in the generated web part bundle. Additionally, the CSS loader processes all referenced images with the same result as when using the require() statement. So eventually, you end up with the same result as when using require() directly in web part's code.

Inconvenient referencing images in the SharePoint Framework

It turns out, that by default, URLs of all referenced assets in SharePoint Framework client-side web parts are relative to the page where the web part is placed rather than relative to the web part bundle. As the web part bundle is located on another server, it doesn't allow you to use relative URLs to correctly resolve paths. And while you could think of building a custom gulp task that post-processes the generated bundle files and fixes all URLs, there is an easier way to have the images automatically point to correct URLs.

Correctly reference images in the SharePoint Framework

As mentioned before, SharePoint Framework uses webpack to process and bundle the solutions code. webpack supports the concept of public paths which can be used to specify alternative URLs for assets. By default the public path in webpack is empty, and a custom path can be specified using the output.publicPath property. Even though the SharePoint Framework doesn't expose its complete webpack config file, it supports extending the webpack configuration to specify additional settings.

You can set the public path for webpack for your SharePoint Framework project by extending the standard gulpfile.js to:

'use strict';

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

build.configureWebpack.mergeConfig({
    additionalConfiguration: (generatedConfiguration) => {
        if (build.getConfig().production) {
            var basePath = build.writeManifests.taskConfig.cdnBasePath;
            if (!basePath.endsWith('/')) {
                basePath += '/';
            }
            generatedConfiguration.output.publicPath = basePath;
        }
        else {
            generatedConfiguration.output.publicPath = "/dist/";
        }
        return generatedConfiguration;
    }
});

build.initialize(gulp);

First, you extend the webpack configuration as mentioned in the guidance on dev.office.com/sharepoint. Then, you check if the current build is a debug or a production build. A debug build uses a localhost URL with the extra dist folder in the path, while a release build points to the CDN URL specified in the ./config/write-manifests.json file in the cdnBasePath property. Having the right URL, you assign it to the generatedConfiguration.output.publicPath property, which will pass it on to webpack. The next time you build your project, all images will be referenced correctly, no matter if you test the web part in the local workbench and refer to images using the img tag:

The img tag of the Rencore logo showed in a SharePoint Framework client-side web part, highlighted in the Chrome developer tools

or CSS:

The CSS properties of a div with the Rencore logo image background displayed in a SharePoint Framework client-side web part, highlighted in the Chrome developer tools

It works even if you deploy the release build of your web part to SharePoint and test it with modern pages:

SharePoint Framework client-side web part on a modern page, showing the Rencore logo

or classic pages:

SharePoint Framework client-side web part on a classic page, showing the Rencore logo

It just works.

Summary

When you try to reference images in your SharePoint Framework solution, by default SharePoint Framework uses URLs relative to the page where the web part is placed. As a result, all image references are broken. By changing the default configuration of webpack, you can have all URLs point to the correct location without changing anything about your code.

Comments

comments powered by Disqus
__aSyNcId_<_zpCTPWBB__