Pi.js Plugin System
Pi.js features a powerful plugin system that allows you to extend the library’s functionality without modifying the core code. Plugins can add new commands, screen-specific features, and custom functionality seamlessly.
What are Plugins?
Plugins are modular extensions that can:
- Add new commands to the Pi.js API
- Add screen-specific commands
- Extend screen data with custom properties
- Hook into screen initialization and cleanup
- Provide additional utilities and features
Plugins work seamlessly with both build formats:
- ESM (ES Modules)
- IIFE (Immediately Invoked Function Expression for <script> tags)
Using Existing Plugins
Browser (IIFE with <script> tags)
The simplest way to use plugins in the browser:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Pi.js with Plugin</title>
</head>
<body>
<!-- Load Pi.js -->
<script src="build/pi.min.js"></script>
<!-- Load plugin (auto-registers itself) -->
<script src="plugins/my-plugin/dist/my-plugin.min.js"></script>
<script>
$.ready( () => {
$.screen( "300x200" );
// Use plugin commands
$.myPluginCommand( "hello" );
} );
</script>
</body>
</html>ES Modules (ESM)
For modern JavaScript projects:
import pi from "./build/pi.esm.min.js";
import myPlugin from "./plugins/my-plugin/dist/my-plugin.esm.min.js";
// Register the plugin
pi.registerPlugin( {
"name": "my-plugin",
"init": myPlugin
} );
pi.ready( () => {
pi.screen( "300x200" );
pi.myPluginCommand( "hello" );
} );Creating Your Own Plugin
Basic Plugin Structure
Create a new directory in plugins/ with your plugin name:
plugins/
my-plugin/
index.js # Source code
README.md # Plugin documentation
package.json # Optional: Plugin metadataMinimal Plugin Example
plugins/my-plugin/index.js:
/**
* My Custom Plugin for Pi.js
*
* @param {Object} pluginApi - Plugin API provided by Pi.js
*/
function myPlugin( pluginApi ) {
// Add a simple command
pluginApi.addCommand( "hello", hello, false, [ "message" ] );
function hello( options ) {
const message = options.message || "Hello, Pi.js!";
console.log( message );
return message;
}
// Add a screen command
pluginApi.addCommand( "drawCustomShape", drawCustomShape, true, [ "x", "y", "size" ] );
function drawCustomShape( screenData, options ) {
const x = options.x || 0;
const y = options.y || 0;
const size = options.size || 10;
screenData.api.rect( x, y, size, size );
}
}
// Auto-register in IIFE mode
if( typeof window !== "undefined" && window.pi ) {
window.pi.registerPlugin( {
"name": "my-plugin",
"version": "1.0.0",
"description": "My custom plugin",
"init": myPlugin
} );
}
// usage:
$.screen( "300x200" );
$.drawCustomShape( 25, 50, 50, 50 );Advanced Plugin Example
plugins/advanced-plugin/index.js:
/**
* Advanced Plugin Example
*
* Demonstrates screen data, initialization hooks, and utilities.
*/
function advancedPlugin( pluginApi ) {
// Add custom data to each screen
pluginApi.addScreenDataItem( "myPluginState", {
"counter": 0,
"enabled": true
} );
// Add initialization hook
pluginApi.addScreenInitFunction( ( screenData ) => {
console.log( `Advanced plugin initialized for screen ${screenData.id}` );
// Set up initial state
screenData.myPluginState.counter = 0;
} );
// Add cleanup hook
pluginApi.addScreenCleanupFunction( ( screenData ) => {
console.log( `Advanced plugin cleanup for screen ${screenData.id}` );
} );
// Add command that uses screen data
pluginApi.addCommand( "incrementCounter", incrementCounter, true, [] );
function incrementCounter( screenData ) {
screenData.myPluginState.counter++;
return screenData.myPluginState.counter;
}
// Add command that accesses the main API
pluginApi.addCommand( "getApiVersion", getApiVersion, false, [] );
function getApiVersion() {
const api = pluginApi.getApi();
return api.version;
}
// Use utility functions
pluginApi.addCommand( "randomColor", randomColor, false, [] );
function randomColor() {
const utils = pluginApi.utils;
const r = Math.floor( utils.rndRange( 0, 256 ) );
const g = Math.floor( utils.rndRange( 0, 256 ) );
const b = Math.floor( utils.rndRange( 0, 256 ) );
return utils.rgbToColor( r, g, b, 255 );
}
}
// Auto-register in IIFE mode
if( typeof window !== "undefined" && window.pi ) {
window.pi.registerPlugin( {
"name": "advanced-plugin",
"version": "1.0.0",
"description": "Advanced plugin demonstrating all features",
"init": advancedPlugin
} );
}
// Usage:
$.screen( "300x200" );
for( let i = 0; i < 5; i += 1 ) {
$.setColor( $.randomColor() );
$.print( "Random Color" );
}Plugin API Reference
The pluginApi object passed to your plugin’s init function provides these methods:
Commands
addCommand( name, fn, isScreen, parameterNames )
Add a global command to the Pi.js API.
- name (string): Command name (will be available as
pi.commandName()) - fn (function): Command function that receives
optionsobject - isScreen (boolean): Whether this is a screen-specific command
- parameterNames (array): Array of parameter names
Example:
pluginApi.addCommand( "myCommand", myFn, false, [ "param1", "param2" ] );
function myFn( options ) {
console.log( options.param1, options.param2 );
}
// Usage:
$.myCommand( "a", "b" ); // Positional
$.myCommand( { "param1": "a", "param2": "b" } ); // NamedScreen Data
addScreenDataItem( name, value )
Add custom data to all screens. The value will be cloned for each screen.
Example:
pluginApi.addScreenDataItem( "myData", {
"value": 0,
"enabled": true
} );
// Access in commands:
function myCommand( screenData ) {
screenData.myData.value++;
}addScreenDataItemGetter( name, fn )
Add dynamic screen data that’s generated when each screen is created.
Lifecycle Hooks
addScreenInitFunction( fn )
Register a function to be called when each screen is initialized.
Example:
pluginApi.addScreenInitFunction( ( screenData ) => {
screenData.myData.initialized = true;
} );addScreenCleanupFunction( fn )
Register a function to be called when a screen is removed.
Example:
pluginApi.addScreenCleanupFunction( ( screenData ) => {
// Clean up resources
screenData.myData = null;
} );Utilities
getApi()
Get reference to the main Pi.js API object.
Example:
const api = pluginApi.getApi();
console.log( api.version );utils
Access to Pi.js utility functions:
parseOptions( args, parameterNames )– Parse function argumentsisFunction( fn )– Check if value is a functionisDomElement( el )– Check if value is a DOM elementisObjectLiteral( obj )– Check if value is a plain objectconvertToColor( color )– Convert color formatsrgbToColor( r, g, b, a )– Create color objectclamp( num, min, max )– Clamp number to rangeinRange( point, hitBox )– Check point in rectanglerndRange( min, max )– Random number in rangedegreesToRadian( deg )– Convert degrees to radiansradiansToDegrees( rad )– Convert radians to degreespadL( str, len, c )– Pad string leftpad( str, len, c )– Pad string centergetInt( val, def )– Parse integer with default- And more…
Building Plugins
Automatic Build
Plugins are built automatically when you build Pi.js:
node scripts/build.jsThis will:
- Build Pi.js in both formats (ESM and IIFE)
- Automatically discover and build all plugins in the
plugins/directory - Generate both minified and unminified versions
- Output plugin builds to
plugins/<plugin-name>/dist/ - Skip any directories without an
index.jsfile
Build Individual Plugin
To build a single plugin during development:
node scripts/build-plugin.js my-pluginThis is useful when you’re actively developing a plugin and don’t want to rebuild Pi.js every time.
Using the Source File Directly
For simple plugins, you can just use the index.js source file directly without building. Modern browsers support ES modules:
<script type="module">
import pi from "./build/pi.esm.min.js";
import myPlugin from "./plugins/my-plugin/index.js";
pi.registerPlugin( {
"name": "my-plugin",
"init": myPlugin
} );
</script>Best Practices
1. Naming Conventions
- Use descriptive, unique command names to avoid conflicts
- Prefix commands with your plugin name if needed:
myPlugin_command() - Follow Pi.js naming conventions (camelCase)
2. Error Handling
- Validate parameters in your commands
- Throw descriptive errors with error codes
- Use try-catch for async operations
Example:
function myCommand( options ) {
if( typeof options.value !== "number" ) {
const error = new TypeError( "myCommand: value must be a number" );
error.code = "INVALID_VALUE";
throw error;
}
// ... rest of implementation
}3. Documentation
- Include JSDoc comments for all functions
- Create a README.md for your plugin
- Document all commands and their parameters
- Provide usage examples
4. Compatibility
- Support both build formats (ESM and IIFE)
- Test in browser environments
- Don’t depend on external libraries unless necessary
- Use Pi.js utilities when available
5. Resource Management
- Clean up resources in cleanup hooks
- Avoid memory leaks
- Don’t modify core Pi.js objects
- Use screen data for per-screen state
6. Testing
- Test your plugin with different screen configurations
- Test command parameter variations
- Test with multiple screens active
- Test screen removal and cleanup
Example Plugins
Simple Drawing Plugin
function drawingTools( pluginApi ) {
// Draw a star
pluginApi.addCommand( "star", star, true, [ "x", "y", "radius", "points" ] );
function star( screenData, options ) {
const x = options.x || 0;
const y = options.y || 0;
const radius = options.radius || 50;
const points = options.points || 5;
const innerRadius = radius * 0.5;
let px1, py1;
for( let i = 0; i < points * 2 + 1; i++ ) {
const r = i % 2 === 0 ? radius : innerRadius;
const angle = ( i * Math.PI ) / points;
const px2 = x + r * Math.cos( angle - Math.PI / 2 );
const py2 = y + r * Math.sin( angle - Math.PI / 2 );
if( i !== 0 ) {
screenData.api.line( px1, py1, px2, py2 );
}
px1 = px2;
py1 = py2;
}
}
}
if( typeof window !== "undefined" && window.$ ) {
$.registerPlugin( {
"name": "drawing-tools",
"version": "1.0.0",
"init": drawingTools
} );
}
// Usage:
$.screen( "300x200" );
$.setColor( 14 );
$.star( 150, 100, 90, 5 );Getting Plugin List
You can check which plugins are registered:
const plugins = $.getPlugins();
$.screen( "300x200" );
for( const plugin of plugins ) {
console.log( plugin );
$.print( plugin.name );
}Support
For questions or issues:
- Check the main Pi.js documentation
- Review the source code in
src/core/plugins.js - Look at example plugins in the plugins directory
- Examine how core modules use the command system
Contributing
To contribute a plugin:
- Create your plugin in the
plugins/directory - Follow the coding conventions in the main Pi.js style guide
- Include comprehensive documentation
- Test thoroughly
- Submit a pull request
Happy plugin development! 🎨