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:
- Duplicate code across controls
- Increased control bundle sizes
- Inconsistent logic and updates
- Slower load times
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:
- Sharing a utility library across multiple PCF controls
- On-demand loading of third-party libraries like TinyMCE
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.
Why UMD and Not Bundled?
UMD (Universal Module Definition) libraries allow shared code to be loaded once and made globally available across controls. In contrast:
- Bundled libraries get embedded inside each PCF control’s final output, causing duplication.
- Every control ends up with its own version, increasing size and complicating updates.
- Shared UMD libraries allow centralized updates, reduced bundle size, and consistent behavior.
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.
Use Case #1: Sharing Utility Libraries Across Controls
Scenario
We were working on two different PCF controls:
- Sales Overview Grid – Displays currency totals for various accounts
- Forecast Chart Control – Shows forecasts and requires formatted numeric values
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.
The Shared Library Component (CommonLibrary)
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:
- pcfAllowCustomWebpack: Enables you to customize how your control is bundled (e.g., exclude external libraries).
- pcfAllowLibraryResources: Lets your control use an external JS file like js without bundling it.
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 { } }
We did the same for the ForecastChartControl or any other PCF control.
Benefits of This Approach
- Reusable logic across all PCF controls
- No code duplication
- Centralized updates – update once, reflect everywhere
- Faster load times and smaller control bundles
- Cleaner code and better maintenance
Use Case #2: Lazy Loading TinyMCE Editor with Dependent Libraries
Scenario:
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:
1. Configure Manifest with On-Demand Load
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.
2. Declare Required Platform Features
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.
3. Create the TinyMCE Stub Library Component
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.
4. Load the Editor Dynamically in the Dependent Control
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));
Benefits:
- Performance Optimization: Editor loads only when needed
- Smaller Initial Bundle Size: Faster load times for unrelated forms
- Better User Experience: Reduced visual clutter and control overhead
Conclusion: Real Productivity Gains
By using dependent libraries in Power Apps PCF controls, We’ve been able to:
- Decouple shared logic into a maintainable utility component
- Improve performance by loading heavy libraries like TinyMCE on demand
- Avoid duplicate code, reduce control size, and speed up rendering
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.