Edit

Share via


Tutorial: Use dependent libraries in a component

[This topic is pre-release documentation and is subject to change.]

This tutorial shows how to build a code component for model-driven apps that is dependent on libraries that are contained in another component. Learn more about the dependent libraries preview

Goal

Follow the steps in this tutorial to create a library control and a control that depends on it. This tutorial contains the following steps:

  1. Build the library component: Create a component that only contains the reusable library. For simplicity, this control only contains the reusable library. There's no reason it couldn't also provide functionality.
  2. Build the dependent control: Create a component that uses the library defined in the library control and add it to a form of a model-driven app to verify that it works.
  3. Load dependent library on demand: Expand on the example to make the dependent component load the library resource on demand rather than have the framework load the library when the control loads.

Prerequisites

You should already know how to:

1. Build the library component

This component doesn't provide any capabilities by itself. It's simply a container for the library.

The first step is to create a new component using the pac pcf init command:

pac pcf init -n StubLibrary -ns SampleNamespace -t field -npm

Define the library

  1. You need a new declaration file (d.ts) to describe the objects and functions contained in your library. Create a new file in the root folder of your project named myLib.d.ts:

    declare module 'myLib' {
      export function sayHello(): string;
    }
    
  2. We are going to expose our library as an UMD module, and we need to put the variable in the global scope. For this we need a new declaration file (d.ts). Create a new file in the root folder of your project named global.d.ts:

    /* eslint-disable no-var */
    declare global {
      var myLib: typeof import('myLib');
    }
    
    export { };
    
  3. Update tsconfig.json to allow UMD modules and javascript code as follows:

    {
        "extends": "./node_modules/pcf-scripts/tsconfig_base.json",
        "compilerOptions": {
            "typeRoots": ["node_modules/@types"]
        }
    }
    

Add the library

In your new control folder, add a new folder to contain your libraries libs for this example create a new JavaScript file. This example uses a library named myLib-v_0_0_1.js that has a single sayHello function.

// UMD module pattern
var myLib = (function (exports) {
"use strict";

function sayHello() {
   return "Hello from myLib";
}

exports.sayHello = sayHello;

return exports;
})(/** @type {import('myLib')}  */ ({}));

Add Configuration data

  1. Add a file named featureconfig.json in the root folder of the project.

  2. Add the following text to the featureconfig.json file:

    {
      "pcfAllowCustomWebpack": "on",
      "pcfAllowLibraryResources": "on"
    }
    

    Learn more about the featureconfig.json file

  3. Add a new webpack.config.js file in the root folder of your project. This configuration data ensures that the libraries aren't bundled with the control output. Bundling isn't necessary because they're already packaged separately when you build the project.

    /* eslint-disable */
    "use strict";
    
    module.exports = {
      externals: {
        "myLib": "myLib"
      },
    }
    

    Learn more about the webpack.config.js file

  4. Add a reference to the library under the resources in the control manifest.

<resources> 
  <code path="index.ts" order="1"/> 
</resources> 

Add the library to window

The last step is to edit the index.ts of the control to bind the library to the window.

import { IInputs, IOutputs } from "./generated/ManifestTypes";

export class StubLibrary
implements ComponentFramework.StandardControl<IInputs, IOutputs>
{
 constructor() {
   // Empty
 }

 public init(
    context: ComponentFramework.Context<IInputs>,
    notifyOutputChanged: () => void,
    state: ComponentFramework.Dictionary,
    container: HTMLDivElement
 ): void {
   // Add control initialization code
 }

 public updateView(context: ComponentFramework.Context<IInputs>): void {
   // Add code to update control view
 }

 public getOutputs(): IOutputs {
    return {};
 }

  public destroy(): void {
    // Add code to cleanup control if necessary
  }
}

The library project should look like this:-

View of the project folder

Build and package the library component

To finish the library component, complete the following steps as usual:

  1. Create and build the code component
  2. Package the code component
  3. Deploy the code component

2. Build the dependent control

Now that you have a library control, you need a control to depend on it.

  1. Create a new component using this command:

    pac pcf init -n DependencyControl -ns SampleNamespace -t field -fw react -npm
    
  2. Add a new feature control file in the root folder of your project called featureconfig.json containing the following text:

    {
      "pcfResourceDependency": "on"
    } 
    
  3. Add the dependent resource in the control manifest.

    Use the schemaName of the dependent control [solution prefix]_[namespace].[control name] which you can find in the solution.xml file for the dependent component. The XML in the solution.xml file might look like this:

    <RootComponents>
      <RootComponent
       type="66"
       schemaName="samples_SampleNamespace.StubLibrary"
       behavior="0"
      />
    </RootComponents>
    
<resources>
      <code path="index.ts"
         order="1" />
      <platform-library name="React"
         version="16.14.0" />
      <platform-library name="Fluent"
         version="9.46.2" />
</resources> 

Add Global.d.ts

Since the StubLibrary is exposed as an UMD module, we need to put the variable in the global scope. For this we need a new declaration file (d.ts). Create a new file in the root folder of your project named global.d.ts:

/* eslint-disable no-var */

interface MyLib {
      sayHello(): string;
}

declare global {
      var myLib: MyLib;
}

export { };

Use the library function

Update the component HelloWorld.tsx file so that it uses a function from the dependent library. The library is loaded into the Window object at runtime.

import * as React from 'react';
import { Label } from '@fluentui/react-components';

export interface IHelloWorldProps {
  name?: string;
}

export class HelloWorld extends React.Component<IHelloWorldProps> {
  public render(): React.ReactNode {
    return (
      <Label>
        {this.props.name}
      </Label>
    )
  }
}

Build and package the dependent component

To finish the dependent component, complete the following steps as usual:

  1. Create and build the code component
  2. Package the code component
  3. Deploy the code component

Add the component to a form

  1. Add the component to the model-driven form.

  2. Navigate to the form and you should see the component show the text Hello from myLib from Dependency.

    Image of component running in an environment

3. Load dependent library on demand

You can expand on this example by changing the dependent component to load the library resource on demand rather than have the framework load the library when the component loads. On demand load behavior is useful if the libraries being used by the control are large and would increase the load time of the form.

Specify on demand load behavior

To specify on demand load behavior, modify the control manifest of the component created in 2. Build the dependent control.

<resources>
    <dependency type="control"
        name="samples_SampleNamespace.StubLibrary"
        order="1" />
    <code path="index.ts"
        order="2" />
    <platform-library name="React" version="16.14.0" />
    <platform-library name="Fluent" version="9.46.2" />
</resources>

Modify the dependent component to load library on demand

Modify the HelloWorld.tsx to add a state and methods to update it once the dependency loads.

import * as React from 'react';
import { Label } from '@fluentui/react-components';

export interface IHelloWorldProps {
  name?: string;
}

export class HelloWorld extends React.Component<IHelloWorldProps> {
  public render(): React.ReactNode {
    return (
      <Label>
        { window.myLib.sayHello() + " from Dependency" || "Hello World"}
      </Label>
    )
  }
}

Update index.ts

When the script is loaded on demand, you need to make slight adjustments to how the component is created and initialized. For example, new variables for references to the context and the container to update the state.

Most importantly add a getActions method to react to the On Load and request the dependent control to be loaded.

import { IInputs, IOutputs } from "./generated/ManifestTypes";
import { HelloWorld, IHelloWorldProps } from "./HelloWorld";
import * as React from "react";

export class DependencyControl implements ComponentFramework.ReactControl<IInputs, IOutputs> {
    private notifyOutputChanged: () => void;

    constructor() {
        // Empty
    }

    public init(
        context: ComponentFramework.Context<IInputs>,
        notifyOutputChanged: () => void,
        state: ComponentFramework.Dictionary
    ): void {
        this.notifyOutputChanged = notifyOutputChanged;
    }

    public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement {
        const props: IHelloWorldProps = { name: 'Power Apps' };
        return React.createElement(
            HelloWorld, props
        );
    }

    public getOutputs(): IOutputs {
        return { };
    }

    public destroy(): void {
        // Add code to cleanup control if necessary
    }
}

Final steps

  1. Update the version number of the control in the ControlManifest.Input.xml and the version in the Solution.xml
  2. Rebuild, package, deploy, and publish the solution with the updated control.

Verify results

Now, when the page loads you see the control load with Loading... displayed.

Image of component while the form loads

Once the page loads, the control updates to display Hello from myLib Dependency On Demand Load.

Image of component once the form has loaded

Dependent Libraries (preview)