Simple and elegant component-based UI library
—
Component enhancement system for adding cross-cutting functionality to all components. Plugins are functions that receive component instances and can modify their behavior, add methods, or enhance functionality.
Installs a plugin that will be applied to all component instances.
/**
* Define a riot plugin
* @param plugin - Function that receives and enhances component instances
* @returns Set containing all installed plugins
*/
function install(plugin: ComponentEnhancer): InstalledPluginsSet;Usage Example:
import { install } from "riot";
// Create a plugin that adds logging to components
const loggingPlugin = (component) => {
const originalMount = component.mount;
const originalUnmount = component.unmount;
component.mount = function(...args) {
console.log(`Mounting component: ${this.name || 'unnamed'}`);
return originalMount.apply(this, args);
};
component.unmount = function(...args) {
console.log(`Unmounting component: ${this.name || 'unnamed'}`);
return originalUnmount.apply(this, args);
};
return component;
};
// Install the plugin
install(loggingPlugin);Removes a previously installed plugin from the system.
/**
* Uninstall a riot plugin
* @param plugin - Plugin function to remove
* @returns Set containing remaining installed plugins
*/
function uninstall(plugin: ComponentEnhancer): InstalledPluginsSet;Usage Example:
import { uninstall } from "riot";
// Remove the logging plugin
uninstall(loggingPlugin);Plugins are functions that receive a component instance and return the enhanced component:
type ComponentEnhancer = <Props extends DefaultProps, State extends DefaultState>(
component: RiotComponent<Props, State>
) => RiotComponent<Props, State>;Method Enhancement Plugin:
const methodEnhancementPlugin = (component) => {
// Add a new method to all components
component.addClass = function(className) {
this.root.classList.add(className);
return this;
};
component.removeClass = function(className) {
this.root.classList.remove(className);
return this;
};
return component;
};
install(methodEnhancementPlugin);State Management Plugin:
const stateHistoryPlugin = (component) => {
const stateHistory = [];
const originalUpdate = component.update;
component.update = function(newState, ...args) {
// Store previous state
stateHistory.push({ ...this.state });
// Add undo functionality
this.undo = () => {
if (stateHistory.length > 0) {
const previousState = stateHistory.pop();
return originalUpdate.call(this, previousState, ...args);
}
};
return originalUpdate.call(this, newState, ...args);
};
return component;
};
install(stateHistoryPlugin);Event Plugin:
const eventEmitterPlugin = (component) => {
const listeners = new Map();
component.on = function(event, callback) {
if (!listeners.has(event)) {
listeners.set(event, []);
}
listeners.get(event).push(callback);
return this;
};
component.emit = function(event, ...args) {
if (listeners.has(event)) {
listeners.get(event).forEach(callback => callback(...args));
}
return this;
};
component.off = function(event, callback) {
if (listeners.has(event)) {
const eventListeners = listeners.get(event);
const index = eventListeners.indexOf(callback);
if (index > -1) {
eventListeners.splice(index, 1);
}
}
return this;
};
return component;
};
install(eventEmitterPlugin);this binding - Use arrow functions or .call()/.apply() for method contextonUnmounted if neededRobust Plugin Example:
const robustPlugin = (component) => {
// Only add functionality if it doesn't exist
if (!component.customMethod) {
component.customMethod = function() {
console.log("Custom functionality");
return this;
};
}
// Enhance existing lifecycle method safely
const originalOnUnmounted = component.onUnmounted;
component.onUnmounted = function(...args) {
// Plugin cleanup logic
console.log("Plugin cleanup");
// Call original if it exists
if (originalOnUnmounted) {
return originalOnUnmounted.apply(this, args);
}
};
return component;
};Plugins are validated when installed:
import { install } from "riot";
// These will throw errors:
install("not a function"); // Error: Plugins must be of type function
install(loggingPlugin); // First install succeeds
install(loggingPlugin); // Error: This plugin was already installed
// These will throw errors when uninstalling:
uninstall(notInstalledPlugin); // Error: This plugin was never installedtype ComponentEnhancer = <
Props extends DefaultProps,
State extends DefaultState
>(
component: RiotComponent<Props, State>
) => RiotComponent<Props, State>;
type InstalledPluginsSet = Set<ComponentEnhancer>;
type DefaultProps = Record<PropertyKey, any>;
type DefaultState = Record<PropertyKey, any>;Install with Tessl CLI
npx tessl i tessl/npm-riot