Building shared code in SharePoint Framework - revisited

A while back, I wrote about building shared code (dll-code) on the SharePoint Framework. It turns out, I missed a few important details. Here is how it should be done.

Sharing code between web parts

When building SharePoint Framework client-side web parts, it's only a matter of time before you will want to reuse some of your code across the different web parts. Reusing your code, rather than copying it to all your web parts, allows you to standardize your solutions, save time building them and speed up loading your web parts on pages. When done correctly, shared code is loaded only once for each page by the SharePoint Framework. And if there are multiple web parts on the same page using it, they will not only load faster due to smaller bundle sizes but also due to the shared code being executed only once.

Sharing code on the SharePoint Framework

When building SharePoint Framework solutions there are two ways in which you can write shared code. You can create a separate Node package, without any specific implementation for the SharePoint Framework, like you would do in any other webstack project, or you can create a SharePoint Framework library. While building SharePoint Framework libraries requires a bit more work, it offers you bigger performance benefits.

In the past I wrote about how you can build a SharePoint Framework library. After additional research it turns out, that I missed a few important details which led to incomplete implementation of the SharePoint Framework library concept.

SharePoint Framework libraries the way they should be

When built correctly, a SharePoint Framework library will be detected by the SharePoint Framework as a component. Even if referenced by multiple web parts on the page, the library will be loaded and executed only once. For a SharePoint Framework library to be correctly detected as a component, it must meet the following requirements:

  • it must be built as a separate Node package
  • it must have a manifest with the component type set to Library
  • the package.json file of the library project, must have the main and typings properties specified. Without them, you will get TypeScript errors when trying to reference the library in other projects
  • in the project referencing the library, the library must be listed as a depedency in the project.json file

To illustrate this in practice, let's consider the following scenario. You're building two web parts that both show information about recently modified documents. One web part shows the last modified document, the other web part last n documents. As the data presented by both web parts is very similar, instead of having both web parts load their data independently, you want to centralize it, so that the data can be loaded once and reused acrossed both web parts.

Two web parts showing information about recently modified documents

To centralize loading data you build a SharePoint Framework service that will load the information about recently modified documents from SharePoint and make that information available to the different component present on the page. That service is distributed as a SharePoint Framework library so that it can be reused by all component that want to access information about the recently modified documents.

Build the data access service in a SharePoint Framework library

The easiest way to create a new SharePoint Framework library, is by scaffolding a new SharePoint Framework project. You can then remove the default web part scaffolded with your project. All other configuration files can be reused to generate the library bundle.

Implement service code

In the ./src folder, create a new folder named services/documentsService. In that folder create a new manifest file for the library. This manifest is very similar to a client-side web part manifest. The key difference is that the componentType property is set to Library.

SharePoint Framework library manifest contents displayed in Visual Studio Code

In the same folder, define interfaces that define your data model. If you're building a SharePoint Framework service you also need to define an interface that specifies the shape of your service. This interface is required to register a SharePoint Framework service with the SharePoint Framework.

Documents service interface contents displayed in Visual Studio Code

Next, implement the service. In this example, the service uses static data, but you could easily extend it to have it load data from SharePoint or another remote API.

Documents service contents displayed in Visual Studio Code

When building a SharePoint Framework service you have to specify a key used to register and consume this service by other components. The name parameter is an arbitrary string without any significance in the registration process.

To finalize building the service, build a barrel (index.ts) to abstract away the internal file structure from components using the library. Using barrels is a common practice that allows you to change the internal file structure without affecting other components referencing the module.

Documents service barrel contents displayed in Visual Studio Code

Specify the SharePoint Framework library entry point

SharePoint Framework libraries are similar to client-side web parts. Just like web parts, libraries must have a manifest and an entry point specified in the ./config/config.json file.

SharePoint Framework library entry point specified in the config.json file

Specify the package entry point

Just like SharePoint Framework client-side web parts, libraries are built in TypeScript. The main benefit of that is, that when referencing libraries in your web parts, you can leverage TypeScript's type safety features and work with your libraries using their original types which in effect helps you write better code. For this to work, you have to specify the entry point of your library and the path to its typings in the package.json file.

The main and typings properties highlighted in the package.json file

Make the library available for referencing on your development machine

When working with SharePoint Framework projects, there is a chance that you will be building web parts and libraries at the same time. To simplify this workflow you can register the library on your local development machine as a Node package and reference it from there. This approach allows you to be more efficient comparing to publishing the library to the package registry after committing each change.

To register the library on your local development machine, in the command line change the working directory to the library project folder and execute:

npm link

Use the data access service from the library to load web part data

At this point, the library and the service are ready to be consumed by your web parts.

Install the library package in your web part project

Previously you registered the SharePoint Framework library as a Node package on your development machine using the npm link command. In order to use it in your web part project, you have to install it. For linked packages, instead of using the npm install command, you use npm link [package name], for example:

npm link react-recentdocuments-service

Keep in mind, that referencing a package using the npm link command, doesn't add it to the package.json file, and for the library to be correctly detected as a SharePoint Framework component, you have to do that manually yourself.

SharePoint Framework library dependency entry highlighted in the package.json file

Consume the data access service from the library

Having referenced the SharePoint Framework library in your web part project, you can now use it as any other Node package. Following code sample illustrates how you would load the information about recently modified documents using the SharePoint Framework service from the library:

export default class RecentDocumentWebPart extends BaseClientSideWebPart<IRecentDocumentWebPartProps> {
  private documentsService: IDocumentsService;

  protected onInit(): Promise<void> {
    return new Promise<void>((resolve: () => void, reject: (error: any) => void): void => {
      const serviceScope: ServiceScope = this.context.serviceScope.getParent();
      serviceScope.whenFinished((): void => {
        this.documentsService = serviceScope.consume(DocumentsService.serviceKey as any) as IDocumentsService;
        resolve();
      });
    });
  }

  // ...
}

SharePoint Framework services are consumed via the service scope. Each client-side web part has its own scope, that allows you to use different implementations for the services. When you however want to use the same instance of the service, you can load it through the parent scope which is shared amongst all web parts on the page.

Passing the service key, you can retrieve the reference to the service and call its methods.

Currently there is a shortcoming in TypeScript where it considers the types from a linked package incompatible with the types used in the service. To work around this limitation, when consuming the service, you have to cast the service key to the any type and then cast the retrieved service to its original type, for example: serviceScope.consume(DocumentsService.serviceKey as any) as IDocumentsService;. Instead of using npm link you can use the Microsoft Rush tool which doesn't have this limitation.

Having two web parts on the page, both loading their data using the shared data access service distributed as a SharePoint Framework library, you can confirm, that the library is loaded and executed only once. Thanks to the implementation of the service, the information about recently modified documents is loaded only once and reused by all web parts on the page.

Page loading log in the Google Chrome developer tools proving that the library and data is loaded once

One more thing

While the concept of SharePoint Framework libraries is powerful and encourages reusing common code, at this moment, it isn't supported beyond the local workbench. First of all, the current version of the SharePoint Framework Yeoman generator doesn't support packaging projects that define components. Even if you managed to create an .sppkg file for your library, you will get an error if you try to install it to the App Catalog in your SharePoint tenant, which is required for the library to be correctly loaded by your web parts.

To encourage Microsoft to implement this concept for us to use, I submitted a UserVoice entry at https://sharepoint.uservoice.com/forums/329220-sharepoint-dev-platform/suggestions/19224202-add-support-for-library-packages-in-the-sharepoint. If you feel like this capability would be beneficial to you when building SharePoint Framework solutions, vote on the idea too and hopefully we will see it shipped in the near future.

Summary

When building SharePoint Framework client-side web parts it's only a matter of time before you will want to reuse some of your code across the different web parts. Reusing your code, rather than copying it to all your web parts, allows you to standardize your solutions, save time building them and speed up loading your web parts on pages. SharePoint Framework offers you building blocks that you can use to build shared code to be reused across the different client-side web parts.

The source code of the sample used in this article is available on GitHub at https://github.com/waldekmastykarz/spfx-react-recentdocuments

Comments

comments powered by Disqus