CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-riot

Simple and elegant component-based UI library

Pending
Overview
Eval results
Files

plugin-system.mddocs/

Plugin System

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.

Capabilities

Install Plugin

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);

Uninstall Plugin

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);

Plugin Development

Component Enhancer Function

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>;

Plugin Patterns

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);

Plugin Best Practices

  1. Always return the component - Plugins must return the component instance
  2. Preserve original methods - Store references to original methods before overriding
  3. Use proper this binding - Use arrow functions or .call()/.apply() for method context
  4. Handle cleanup - Add cleanup logic in onUnmounted if needed
  5. Check for existing functionality - Test if methods/properties already exist before adding

Robust 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;
};

Error Handling

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 installed

Types

type 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

docs

compilation.md

component-factory.md

component-registration.md

index.md

mounting-lifecycle.md

plugin-system.md

pure-components.md

utilities.md

tile.json