Building Custom JavaScript Components
This document outlines how to build custom Universal app components.
Universal is extensible and you can build custom JavaScript components and frameworks. This document will cover how to build custom components that integrate with the Universal app platform.
This is an advanced topic and not required if you simply want to use Universal Apps.
Overview
Building a custom React component for PowerShell Universal involves several key pieces:
Project Structure: A Node.js project with Webpack for bundling React components
React Component (JSX): The React component that renders your UI
PowerShell Module (PSM1): PowerShell functions that create component definitions
Module Manifest (PSD1): Standard PowerShell module metadata
Build Process: Webpack configuration to bundle JavaScript assets
Component Registration: Registering your component with Universal Dashboard
How It Works
The integration between PowerShell and React works like this:
PowerShell Side: Your PowerShell function returns a hashtable with component properties
Asset Registration: The bundled JavaScript is registered with PowerShell Universal's AssetService
Component Type: The
typeproperty links the PowerShell hashtable to the React componentProp Passing: Hashtable properties automatically become React props
Rendering: Universal Dashboard loads the JavaScript bundle and renders the React component
PowerShell Function → Hashtable → Asset Service → React Component → DOMThe ud-mermaid Example
Throughout this guide, we'll reference the ud-mermaid project as a real-world example. This component wraps the Mermaid.js diagramming library for use in PowerShell Universal, demonstrating:
Third-party JavaScript library integration
React hooks usage (useEffect, useRef)
Configuration object passing
Professional project structure
Build automation
Step-By-Step
This following section will take you step-by-step through the different aspects of building a Universal App component.
1. Installing Dependencies
You will need to install the following dependencies before creating your component.
NodeJS - Required for npm and running build tools
2. Create a New Project
Create a new directory for your component project:
New-Item -Path .\MyComponent -ItemType Directory
Set-Location .\MyComponentInitialize a new npm project:
npm init -yThis creates a basic project structure including:
package.json- Node.js dependencies and build scripts
3. Install JavaScript Dependencies
Install the required build tools and React dependencies:
npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader webpack webpack-cli css-loader style-loader @babel/plugin-proposal-class-properties @babel/plugin-syntax-dynamic-import --legacy-peer-depsInstall the Universal Dashboard package:
npm install universal-dashboard --legacy-peer-depsFor example, the ud-mermaid project includes the mermaid package as an additional dependency:
"dependencies": {
"mermaid": "^9.4.3",
"universal-dashboard": "^1.0.1"
}Install any additional libraries your component needs:
npm install mermaid --legacy-peer-deps4. Create Project Structure
Create the necessary directories for your component:
New-Item -Path .\Components -ItemType Directory5. Create Your React Component
Create a React component in the Components/ directory. Your component should:
Import from
universal-dashboardUse the
withComponentFeaturesHOC (Higher-Order Component)Accept props that match your PowerShell function parameters
Example from ud-mermaid (Components/mermaid.jsx):
import React, { useEffect, useRef } from 'react';
import { withComponentFeatures } from 'universal-dashboard';
import mermaid from 'mermaid';
const UDMermaid = (props) => {
const mermaidRef = useRef(null);
const { id, diagram, config } = props;
useEffect(() => {
mermaid.initialize(config || {});
if (mermaidRef.current) {
mermaidRef.current.removeAttribute('data-processed');
mermaid.contentLoaded();
}
}, [diagram, config]);
return (
<div className="mermaid" id={id} ref={mermaidRef}>
{diagram}
</div>
);
};
export default withComponentFeatures(UDMermaid);6. Register Your Component
Create an index.js file in the Components/ directory that registers your component with Universal Dashboard:
import UDMermaid from './mermaid';
UniversalDashboard.register("ud-mermaid", UDMermaid);The string you pass to register() becomes the type property you'll use in your PowerShell function.
7. Create PowerShell Functions
Now you will need to author the PowerShell module code. You will need to update the PSM1 file to load assets and define functions that create component definitions.
The PSM1 file should:
Register the bundled JavaScript file with the AssetService
Define functions that return hashtables with component properties
Example from ud-mermaid (UniversalDashboard.Mermaid.psm1):
# Register JavaScript assets with PowerShell Universal
Get-ChildItem "$PSScriptRoot\*.js" | ForEach-Object {
$Item = [UniversalDashboard.Services.AssetService]::Instance.RegisterAsset($_.FullName)
if ($_.Name.StartsWith("index.") -and $_.Name.EndsWith(".bundle.js")) {
$AssetId = $Item
}
}
function New-UDMermaid {
param(
[Parameter()]
[string]$Id = (New-Guid).ToString(),
[Parameter(Mandatory)]
[string]$Diagram,
[Parameter()]
[hashtable]$Config
)
@{
assetId = $AssetId
isPlugin = $true
type = "ud-mermaid" # This matches the name used in UniversalDashboard.register()
id = $Id
diagram = $Diagram
config = $Config
}
}Key hashtable properties:
assetId- The ID returned from RegisterAssetisPlugin- Always set to$truefor custom componentstype- Must match the name you used inUniversalDashboard.register()id- A unique identifier for the component instanceAdditional properties are passed as props to your React component
8. Configure Webpack
Your webpack.config.js should bundle your components and externalize React and Universal Dashboard dependencies. Here's the essential configuration from ud-mermaid:
module.exports = (env) => {
return {
entry: {
'index': __dirname + '/components/index.js'
},
output: {
path: BUILD_DIR,
filename: '[name].[hash].bundle.js',
library: 'udcomponent',
libraryTarget: 'var'
},
module: {
rules: [
{ test: /\.(js|jsx)$/, exclude: [/public/], loader: 'babel-loader' },
{ test: /\.css$/, loader: "style-loader!css-loader" }
]
},
externals: {
'react': 'react',
'react-dom': 'reactdom',
UniversalDashboard: 'UniversalDashboard'
},
resolve: {
extensions: ['.js', '.jsx']
}
};
}Important externals:
reactandreact-dom- Provided by PowerShell UniversalUniversalDashboard- The Universal Dashboard global object
8.1. Configure Babel
Create a .babelrc file to configure JSX and modern JavaScript transformation:
{
"presets": [
["@babel/preset-env", {
"targets": {
"browsers": [">0.5%", "not dead"]
}
}],
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator"
]
}This configuration:
Transforms JSX to JavaScript
Transpiles modern JavaScript for browser compatibility
Enables useful JavaScript language features
9. Create Module Manifest
Create a standard PowerShell module manifest (.psd1) with your component metadata:
@{
RootModule = 'UniversalDashboard.Mermaid.psm1'
ModuleVersion = '1.0.0'
Author = 'Your Name'
Description = 'Custom component description'
FunctionsToExport = @('New-UDMermaid')
}10. Build The Project
You can now build your project. It will output a module you can load into PowerShell Universal.
First, add build scripts to your package.json:
"scripts": {
"build": "webpack -p --env production",
"dev": "webpack-dev-server --config webpack.config.js -p --env development"
}Then run the build:
npm run buildOptional: Create a build script (like ud-mermaid's component.build.ps1):
# component.build.ps1
$OutputPath = "$PSScriptRoot\output"
Remove-Item -Path $OutputPath -Force -ErrorAction SilentlyContinue -Recurse
Remove-Item -Path "$PSScriptRoot\public" -Force -ErrorAction SilentlyContinue -Recurse
npm install --legacy-peer-deps
npm run build
New-Item -Path $OutputPath -ItemType Directory
Copy-Item $PSScriptRoot\public\*.* $OutputPath
Copy-Item $PSScriptRoot\UniversalDashboard.MyComponent.psd1 $OutputPath
Copy-Item $PSScriptRoot\UniversalDashboard.MyComponent.psm1 $OutputPathThen run it:
.\component.build.ps1The build process:
Bundles all JavaScript/React code using Webpack
Outputs bundled files with hash names (e.g.,
index.78a6d857.bundle.js)Copies module files (
.psm1,.psd1) to the output directory
11. Use in PowerShell Universal
Within your app, load your module and execute the function.
Import-Module .\output\UniversalDashboard.Mermaid.psd1
New-UDApp -Content {
New-UDMermaid -Diagram @"
graph TD
A[Start] --> B[Process]
B --> C[End]
"@
}Project Structure Example
Here's the typical structure of a custom component project (from ud-mermaid):
project/
├── Components/
│ ├── index.js # Component registration
│ └── mermaid.jsx # React component
├── output/ # Build output (git ignored)
│ ├── index.[hash].bundle.js
│ ├── UniversalDashboard.Mermaid.psm1
│ └── UniversalDashboard.Mermaid.psd1
├── package.json # Node.js dependencies
├── webpack.config.js # Webpack configuration
├── component.build.ps1 # Optional build script
├── UniversalDashboard.Mermaid.psm1 # PowerShell module
└── UniversalDashboard.Mermaid.psd1 # Module manifestProps
Props are values that are either passed from the PowerShell hashtable provided by the user or by the Universal App withComponentsFeature high-order function.
Standard
The properties that you set in your hashtable in PowerShell will automatically be sent in as props to React component.
For example, if you set the diagram and config properties in the hashtable:
function New-UDMermaid {
param(
[Parameter()]
[string]$Id = (New-Guid).ToString(),
[Parameter(Mandatory)]
[string]$Diagram,
[Parameter()]
[hashtable]$Config
)
@{
type = "ud-mermaid"
isPlugin = $true
assetId = $AssetId
id = $Id
diagram = $Diagram
config = $Config
}
}Then you will have access to those props in React:
import React, { useEffect, useRef } from 'react';
import { withComponentFeatures } from 'universal-dashboard';
import mermaid from 'mermaid';
const UDMermaid = (props) => {
const { id, diagram, config } = props;
useEffect(() => {
mermaid.initialize(config || {});
}, [diagram, config]);
return <div className="mermaid" id={id}>{diagram}</div>;
};
export default withComponentFeatures(UDMermaid);Best Practices for Props:
Use descriptive prop names that match PowerShell parameter names
Handle optional props with default values or conditional logic
Hashtables in PowerShell become JavaScript objects automatically
React Hooks and Component Lifecycle
When building custom components, you can use all standard React hooks. The ud-mermaid component demonstrates using useEffect and useRef for managing component lifecycle and DOM references:
import React, { useEffect, useRef } from 'react';
const UDMermaid = (props) => {
const mermaidRef = useRef(null);
const { diagram, config } = props;
// Run when diagram or config changes
useEffect(() => {
mermaid.initialize(config || {});
if (mermaidRef.current) {
mermaidRef.current.removeAttribute('data-processed');
mermaid.contentLoaded();
}
}, [diagram, config]); // Dependency array
return <div ref={mermaidRef}>{diagram}</div>;
};Common patterns:
useEffect- For initialization, cleanup, and responding to prop changesuseRef- For accessing DOM elements directlyuseState- For managing internal component stateuseMemo/useCallback- For performance optimization
Endpoints
Endpoints are special in the way they are registered and the way that they are passed as props to your component. You will need to call Register on the endpoint in PowerShell and pass in the Id and PSCmdlet variables.
function New-UD95Button {
param(
[Parameter()]
[string]$Id = [Guid]::NewGuid(),
[Parameter()]
[string]$Text,
[Parameter()]
[Endpoint]$OnClick
)
if ($OnClick)
{
$OnClick.Register($Id, $PSCmdlet)
}
@{
type = "ud95-button"
isPlugin = $true
assetId = $AssetId
id = $Id
text = $Text
onClick = $OnClick
}
}Endpoints are created from ScriptBlocks and are executed when that event happens.
New-UD95Button -Text 'Hello' -OnClick {
Show-UDToast -Message 'Test'
}Universal will automatically wire up the endpoint to a function within JavaScript. This means that you can use the props to call that endpoint.
Notice the props.onClick function call. This will automatically call the PowerShell script block on the server.
import React from 'react';
import { withComponentFeatures } from 'universal-dashboard';
import { Button } from 'react95';
const UD95Button = props => {
const p = {
onClick: () => props.onClick()
}
return <Button {...p}>{props.text}</Button>
}
export default withComponentFeatures(UD95Button);setState
The setState prop is used to set the state of the component. This ensures that the state is tracked and your component will work with Get-UDElement.
For example, with a text field, you'll want to call props.setState and pass in the new text value for the state.
const UDTextField = (props) => {
const onChange = (e) => {
props.setState({value: e.target.value})
}
return <TextField {...props} onChange={onChange} />
}
export default withComponentFeatures(UDTextField);children
The children prop is a standard React prop. If your component supports child items, such as a list or select box, you should use the standard props.children prop to ensure that the cmdlets Add-UDElement , Remove-UDElement and Clear-UDElement function correctly.
Troubleshooting and Debugging
Common Issues
Component not rendering:
Verify the
typein your PowerShell function matchesUniversalDashboard.register()nameCheck that the asset is properly registered with AssetService
Ensure
isPluginis set to$trueConfirm the bundled JavaScript file exists in the module directory
Props not passing correctly:
Verify property names match between PowerShell hashtable and React component
Check browser console for JavaScript errors
Use React DevTools to inspect component props
Build failures:
Run
npm install --legacy-peer-depsto ensure dependencies are installedCheck for syntax errors in JSX files
Verify webpack.config.js externals are correctly configured
Ensure babel is configured properly for JSX transformation
Debugging Tips
Browser Console: Open browser developer tools (F12) to see JavaScript errors and warnings.
React DevTools: Install React DevTools browser extension to inspect component hierarchy and props.
PowerShell Debugging: Use Write-Host or Write-Debug in your PowerShell functions to trace execution.
Webpack Dev Server: During development, use webpack-dev-server for hot reloading:
npm run devThen configure PowerShell Universal to load from the dev server URL.
Example: Complete Component Workflow
Here's a complete example based on the ud-mermaid project:
1. Create React component (Components/mermaid.jsx):
import React, { useEffect, useRef } from 'react';
import { withComponentFeatures } from 'universal-dashboard';
import mermaid from 'mermaid';
const UDMermaid = (props) => {
const mermaidRef = useRef(null);
const { id, diagram, config } = props;
useEffect(() => {
mermaid.initialize(config || {});
if (mermaidRef.current) {
mermaidRef.current.removeAttribute('data-processed');
mermaid.contentLoaded();
}
}, [diagram, config]);
return <div className="mermaid" id={id} ref={mermaidRef}>{diagram}</div>;
};
export default withComponentFeatures(UDMermaid);2. Register component (Components/index.js):
import UDMermaid from './mermaid';
UniversalDashboard.register("ud-mermaid", UDMermaid);3. Create PowerShell function (UniversalDashboard.Mermaid.psm1):
Get-ChildItem "$PSScriptRoot\*.js" | ForEach-Object {
$Item = [UniversalDashboard.Services.AssetService]::Instance.RegisterAsset($_.FullName)
if ($_.Name.StartsWith("index.") -and $_.Name.EndsWith(".bundle.js")) {
$AssetId = $Item
}
}
function New-UDMermaid {
param(
[Parameter()]
[string]$Id = (New-Guid).ToString(),
[Parameter(Mandatory)]
[string]$Diagram,
[Parameter()]
[hashtable]$Config
)
@{
assetId = $AssetId
isPlugin = $true
type = "ud-mermaid"
id = $Id
diagram = $Diagram
config = $Config
}
}4. Build and test:
# Build
npm run build
# Test in PowerShell Universal
Import-Module .\output\UniversalDashboard.Mermaid.psd1
New-UDApp -Content {
New-UDMermaid -Diagram @"
graph TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[Car]
"@
}Additional Resources
ud-mermaid GitHub Repository: https://github.com/ironmansoftware/ud-mermaid - Complete working example
React Documentation: https://react.dev
Webpack Documentation: https://webpack.js.org
Last updated
Was this helpful?