In this blog, we’ll explore how using dependent libraries can significantly improve maintainability, reusability, and performance in Power Apps PCF controls. Through practical scenarios, we’ll uncover how this architectural choice reduces bundle size and streamlines development.
To illustrate this, let’s dive into a recent scenario our team encountered and how dependent libraries transformed our development process.
Recently at work, our team was assigned to build two different PCF controls – one for showing a sales summary and another for displaying a forecast chart. Even though these were two separate controls, both of them needed the same kinds of utility functions and also used the same third-party libraries, such as input validators and rich text editors.
Initially, we bundled these libraries separately within each control. But that approach caused several issues:
As the application suite started to grow, these problems began to affect performance and maintainability.
That’s when we explored the concept of dependent libraries, a feature supported by Power Apps that completely changed the way we architect our controls.
We will walk through the use cases where dependent libraries made a significant difference in the project:
The blog also discusses why the shared UMD-based external library approach was chosen over bundling directly within each control, and how UMD vs. bundled libraries differ in the PCF context.
UMD (Universal Module Definition) libraries allow shared code to be loaded once and made globally available across controls. In contrast:
Approach Used in This Blog: We’re using the UMD external library approach by creating a separate PCF component (like CommonLibrary) which exposes utilities globally. Then, other PCF controls can declare a dependency on this component and load it via loadDependency. This provides clean separation of concerns and optimized performance.
We were working on two different PCF controls:
Both controls needed consistent logic for formatting currency values and validating numbers. Rather than duplicating this logic in both controls, we decided to centralize it using a shared library.
To achieve this, we created a Library Component. According to Microsoft’s guidance, “This component can provide some functionality or only be a container for the library.” In our case, it served as a container that simply injected shared JavaScript functions.
Note: This should be your folder structure and in place of dist folder you can use lib
Both PCF controls can use the shared CommonUtils.js file, eliminating the need to duplicate code.
We created a dedicated PCF control named CommonLibrary. It doesn’t render anything visually but is responsible for injecting a shared JavaScript file (common-utils.js) into the page.
Here are a few functions that were commonly used in both the Sales Overview Grid and Forecast Chart Control. This utility library contains functions that handle currency formatting, input validation, percentage calculation, and empty string checks.
Here’s the content of dist/commonUtils.js using a UMD wrapper:
var CommonUtils = (function (exports) { "use strict"; function formatCurrency(amount: any, currencyCode?: string): string { const numericValue = parseFloat(amount as any) || 0; const formatter = new Intl.NumberFormat("en-IN", { style: "currency", currency: currencyCode || "INR", minimumFractionDigits: 2, }); return formatter.format(numericValue); } function isValidNumber(input: any, options = {}) { const minimum = options.min ?? Number.MIN_SAFE_INTEGER; const maximum = options.max ?? Number.MAX_SAFE_INTEGER; if (input == null || (typeof input === "string" && input === "")) return false; const parsed = Number(input); return !Number.isNaN(parsed) && parsed >= minimum && parsed <= maximum; } function calculatePercentage(part: number, whole: number, decimals: number = 2) { if (!isValidNumber(part) || !isValidNumber(whole) || whole === 0) return 0; return Number(((part / whole) * 100).toFixed(decimals)); } exports.formatCurrency = formatCurrency; exports.isValidNumber = isValidNumber; exports.calculatePercentage = calculatePercentage; return exports; })(typeof CommonUtils === "undefined" ? {} : CommonUtils);
Step-by-Step: How We Set This Up
First we scaffolded the control.
pac pcf init -n CommonLibrary -ns MyCompany -t field -npm
cd CommonLibrary
Drop-in the helpers
Created dist/commonUtils.js:
// UMD wrapper so the module can be imported *or* attached to window var CommonUtils = (function (exports) { "use strict"; /** * Convert any numeric input into localised currency – defaults to INR. */ function formatCurrency(amount: any, currencyCode?: string): string { const numericValue = parseFloat(amount as any) || 0; const formatter = new Intl.NumberFormat("en-IN", { style: "currency", currency: currencyCode || "INR", minimumFractionDigits: 2, }); return formatter.format(numericValue); } /** * Light‑weight validator – NaN guard + optional range check. */ function isValidNumber(input: any, options = {}) { const minimum = options.min ?? Number.MIN_SAFE_INTEGER; const maximum = options.max ?? Number.MAX_SAFE_INTEGER; if (input == null || (typeof input === "string" && input === "")) return false; const parsed = Number(input); return !Number.isNaN(parsed) && parsed >= minimum && parsed <= maximum; } /** * Percentage with configurable precision and full safety nets. */ function calculatePercentage(part: number, whole: number, decimals:number = 2) { if (!isValidNumber(part) || !isValidNumber(whole) || whole === 0) return 0; return Number(((part / whole) * 100).toFixed(decimals)); } exports.formatCurrency = formatCurrency; exports.isValidNumbe = isValidNumber; exports.calculatePercentage = calculatePercentage; return exports; })(typeof CommonUtils === "undefined" ? {} : CommonUtils);
Ship TypeScript types (optional but polite)
The objects and functions in the library must be described in a new declaration file (d.ts).
So we created a new file called common-utils.d.ts and placed it in the project’s root folder:
common-utils.d.ts declare module "CommonUtils" { export function formatCurrency(amount: number, currencyCode?: string): string; export function isValidNumber(input: any, options?: { min?: number; max?: number }): boolean; export function calculatePercentage(part: number, whole: number, decimals?: number): number; } We included the variable in the global scope to expose our library as a UMD module. This required a new declaration file (.d.ts), which we added to the root directory of our project global.d.ts /* eslint-disable no-var */ declare global { var CommonUtils: typeof import("CommonUtils"); } export { }; tsconfig.json which is used in our controls: { "extends": "./node_modules/pcf-scripts/tsconfig_base.json", "compilerOptions": { "allowJs": true, "allowUmdGlobalAccess": true, "typeRoots": ["node_modules/@types"], "outDir": "dist" } }
Enable preview flags & keep webpack from rebundling:
The image below shows the featureconfig.json file that we added to the root directory of our project.
featureconfig.json { "pcfAllowCustomWebpack": "on", "pcfAllowLibraryResources": "on" }
Why this is important:
These settings are essential when working with shared libraries because they give you full control over how dependencies are treated and ensure your control stays lean.
Configure Webpack to Treat Library as External webpack.config.js /* eslint-disable */
“use strict”; module.exports = { externals: { “CommonUtils”: “CommonUtils” } };
Why?
This ensures Webpack won’t include CommonUtils in your final bundle. Instead, it will expect it to be loaded externally — reducing control size and ensuring consistency across multiple controls.
Wire the manifest of CommonLibrabry Component
<resources> <library name="CommonUtils" version=">=1" order="1"> <packaged_library path="dist/commonUtils.js" version="0.0.1" /> </library> <code path="index.ts" order="2" /> </resources>
Expose the helpers on window
import * as CommonUtils from "CommonUtils"; import { IInputs, IOutputs } from "./generated/ManifestTypes"; export class CommonLibrary implements ComponentFramework.StandardControl<IInputs, IOutputs> { public init() { } public updateView() { } public getOutputs(): IOutputs { const outputs: IOutputs = {}; return outputs; } public destroy() { } } // one-liner that puts the module on the global scope – key for consumers (function () { window.CommonUtils = CommonUtils; })();
now we used this common library in other PCF controls
For example one of our control which is SalesOverviewGrid
First we Scaffolded + flag resource dependencies
pac pcf init -n SalesOverviewGrid -ns MyCompany -t field -npm cd SalesOverviewGrid featureconfig.json { "pcfResourceDependency": "on" } We Included the dependency declaration in the SalesOverviewGrid manifest file. <resources> <dependency type="control" name="publisher_MyCompany.CommonLibrary" order="1" /> <code path="index.ts" order="2" /> </resources>
Load the lib, render numbers
This is the sample index.ts for SalesOverviewGrid control
import { IInputs, IOutputs } from "./generated/ManifestTypes"; // Declare the external library attached to window declare const window: { CommonUtils: { formatCurrency: (amount: number, currencyCode?: string) => string; }; }; export class SalesOverviewGrid implements ComponentFramework.StandardControl<IInputs, IOutputs> { private table!: HTMLTableElement; private context!: ComponentFramework.Context<IInputs>; public init(ctx: ComponentFramework.Context<IInputs>): void { this.context = ctx; this.table = document.createElement("table"); ctx.container.appendChild(this.table); } public updateView(ctx: ComponentFramework.Context<IInputs>): void { // Load external library using the recommended loadDependency approach ctx.utils.loadDependency?.("publisher_MyCompany.CommonLibrary") .then(() => this.render()) .catch((e) => console.error("CommonUtils load error", e)); } private render(): void { const monthlySums = [1234.56, 9876.54, 13579.24]; this.table.innerHTML = ""; monthlySums.forEach(v => { const row = this.table.insertRow(); row.insertCell().innerText = window.CommonUtils.formatCurrency(v, "INR"); }); } public getOutputs(): IOutputs { const outputs: IOutputs = {}; return outputs; } public destroy(): void { } }
Benefits of This Approach
We needed to include a rich text editor in a custom PCF control—specifically for enhancing notes on records. We chose TinyMCE because of its full-featured capabilities. However, including it on every load significantly increased the initial load time.
Solution: On-Demand Dependent Library
To optimize performance, we wanted to load TinyMCE only when the control actually required it—such as when the text field became active
Steps to Implement:
We used load-type=’onDemand’ to declare the dependency in the control’s manifest:
<platform-action action-type="afterPageLoad" /> <feature-usage> <uses-feature name="Utility" required="true" /> </feature-usage> <resources> <code path="index.ts" order="1" /> <dependency type="control" name=Publisher_MyCompany.TinyMCEStub" load-type="onDemand" /> </resources>
This tells the platform to load this dependency only when explicitly requested.
We updated the manifest to declare platform usage features:
<platform-action action-type="afterPageLoad" /> <feature-usage> <uses-feature name="Utility" required="true" /> </feature-usage>
This ensures on-demand loading is supported during page lifecycle execution.
We Built a lightweight control that only loads the TinyMCE script:
export class TinyMCEStub implements ComponentFramework.StandardControl<{}, {}> { public init(): void { const script = document.createElement("script"); script.src = "https://cdn.tiny.cloud/1/no-api-key/tinymce/6/tinymce.min.js"; script.async = true; document.head.appendChild(script); } public updateView(): void {} public getOutputs(): IOutputs { const outputs: IOutputs = {}; return outputs; } public destroy(): void {} }
Once this component is defined, it can be reused anywhere you want TinyMCE available.
Whenever TinyMCE functionality is needed, we loaded it dynamically like below:
context.utils.loadDependency?.("publisher_MyCompany.TinyMCE") .then(() => { (window as any).tinymce.init({ target: document.getElementById("editor") }); }) .catch((err: any) => console.error("TinyMCE load error:", err));
By using dependent libraries in Power Apps PCF controls, We’ve been able to:
These patterns not only made our PCF development more efficient but also enhanced the maintainability of the solutions we delivered. Whether you’re building a complex UI component or just trying to avoid redundancy, dependent libraries are an underrated but powerful feature in the Power Apps toolkit.
The post Boost Reusability and Performance in Power Apps Controls Framework with Dependent Libraries first appeared on Microsoft Dynamics 365 CRM Tips and Tricks.