plugin

package
v0.0.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 15, 2026 License: MIT Imports: 15 Imported by: 0

README

Plugin System

The ForgeUI plugin system provides a flexible and extensible architecture for adding functionality to your ForgeUI applications.

Features

  • Plugin Interface: Simple interface for creating plugins
  • Dependency Management: Semver-based dependency resolution
  • Lifecycle Management: Automatic initialization and shutdown in dependency order
  • Hook System: Extensibility points for intercepting rendering and lifecycle events
  • Thread-Safe: Concurrent-safe registry operations
  • Plugin Discovery: Optional dynamic plugin loading (experimental)

Quick Start

Creating a Plugin
package myplugin

import (
    "context"
    "github.com/xraph/forgeui/plugin"
)

type MyPlugin struct {
    *plugin.PluginBase
}

func New() *MyPlugin {
    return &MyPlugin{
        PluginBase: plugin.NewPluginBase(plugin.PluginInfo{
            Name:        "my-plugin",
            Version:     "1.0.0",
            Description: "My awesome plugin",
            Author:      "Your Name",
            License:     "MIT",
        }),
    }
}

func (p *MyPlugin) Init(ctx context.Context, r *plugin.Registry) error {
    // Initialize your plugin here
    return nil
}

func (p *MyPlugin) Shutdown(ctx context.Context) error {
    // Clean up resources here
    return nil
}
Using Plugins
package main

import (
    "context"
    "log"
    
    "github.com/xraph/forgeui/plugin"
    "yourapp/plugins/myplugin"
)

func main() {
    ctx := context.Background()
    
    // Create registry
    registry := plugin.NewRegistry()
    
    // Register plugins
    registry.Use(
        myplugin.New(),
        // Add more plugins...
    )
    
    // Initialize all plugins
    if err := registry.Initialize(ctx); err != nil {
        log.Fatal(err)
    }
    defer registry.Shutdown(ctx)
    
    // Your application code here...
}

Dependencies

Plugins can declare dependencies on other plugins:

func New() *MyPlugin {
    return &MyPlugin{
        PluginBase: plugin.NewPluginBase(plugin.PluginInfo{
            Name:    "my-plugin",
            Version: "1.0.0",
            Dependencies: []plugin.Dependency{
                {Name: "other-plugin", Version: ">=1.0.0"},
                {Name: "optional-plugin", Version: ">=2.0.0", Optional: true},
            },
        }),
    }
}
Version Constraints

The plugin system supports standard semver constraints:

  • =1.2.3 - Exact version
  • >1.2.3 - Greater than
  • >=1.2.3 - Greater than or equal
  • <1.2.3 - Less than
  • <=1.2.3 - Less than or equal
  • ~1.2.3 - Patch updates (>=1.2.3 and <1.3.0)
  • ^1.2.3 - Minor updates (>=1.2.3 and <2.0.0)

Hooks

Plugins can register hooks to intercept various events:

func (p *MyPlugin) Init(ctx context.Context, r *plugin.Registry) error {
    // Register a hook
    r.Hooks().On(plugin.HookBeforeRender, func(hctx *plugin.HookContext) error {
        // Do something before rendering
        return nil
    })
    
    return nil
}
Available Hooks

Lifecycle Hooks:

  • before_init - Before plugin initialization
  • after_init - After plugin initialization
  • before_shutdown - Before plugin shutdown
  • after_shutdown - After plugin shutdown

Render Hooks:

  • before_render - Before page render
  • after_render - After page render
  • before_head - Before <head> content
  • after_head - After <head> content
  • before_body - Before <body> content
  • after_body - After </body> (scripts area)
  • before_scripts - Before script tags
  • after_scripts - After script tags

Plugin Discovery (Experimental)

The plugin system supports dynamic plugin loading from .so files:

registry := plugin.NewRegistry()

// Discover plugins from directory
if err := registry.Discover("./plugins"); err != nil {
    log.Printf("Discovery error: %v", err)
}

// Or use safe discovery (continues on error)
errs := registry.DiscoverSafe("./plugins")
for _, err := range errs {
    log.Printf("Discovery error: %v", err)
}

Important Limitations:

  • Only supported on Linux, FreeBSD, and macOS
  • Must be built with the same Go version as the main program
  • Must use the same versions of all dependencies
  • Cannot be unloaded once loaded

For production use, consider statically linking plugins instead.

Plugin Types

ForgeUI provides specialized plugin types for common use cases:

Component Plugins

Component plugins extend ForgeUI with new UI components:

package chartplugin

import (
    g "maragu.dev/gomponents"
    "maragu.dev/gomponents/html"
    "github.com/xraph/forgeui/plugin"
)

type ChartPlugin struct {
    *plugin.ComponentPluginBase
}

func New() *ChartPlugin {
    return &ChartPlugin{
        ComponentPluginBase: plugin.NewComponentPluginBase(
            plugin.PluginInfo{
                Name:    "charts",
                Version: "1.0.0",
            },
            map[string]plugin.ComponentConstructor{
                "LineChart": lineChartConstructor,
                "BarChart":  barChartConstructor,
            },
        ),
    }
}

func lineChartConstructor(props any, children ...g.Node) g.Node {
    opts := props.(*ChartOptions)
    return html.Div(
        html.Class("chart-container"),
        g.Attr("data-chart-type", "line"),
        g.Attr("data-chart-data", opts.DataJSON()),
        g.Group(children),
    )
}

Using component plugins:

registry := plugin.NewRegistry()
registry.Use(chartplugin.New())
registry.Initialize(ctx)

// Collect all components from plugins
components := registry.CollectComponents()
lineChart := components["LineChart"]
Alpine.js Plugins

Alpine plugins extend Alpine.js with scripts, directives, stores, and magic properties:

package sortableplugin

import "github.com/xraph/forgeui/plugin"

type SortablePlugin struct {
    *plugin.AlpinePluginBase
}

func New() *SortablePlugin {
    return &SortablePlugin{
        AlpinePluginBase: plugin.NewAlpinePluginBase(plugin.PluginInfo{
            Name:    "sortable",
            Version: "1.0.0",
        }),
    }
}

func (p *SortablePlugin) Scripts() []plugin.Script {
    return []plugin.Script{
        {
            Name:     "sortablejs",
            URL:      "https://cdn.jsdelivr.net/npm/[email protected]/Sortable.min.js",
            Priority: 10,
            Defer:    true,
        },
    }
}

func (p *SortablePlugin) Directives() []plugin.AlpineDirective {
    return []plugin.AlpineDirective{
        {
            Name: "sortable",
            Definition: `
                (el, { expression, modifiers }, { evaluate }) => {
                    let options = expression ? evaluate(expression) : {};
                    new Sortable(el, options);
                }
            `,
        },
    }
}

func (p *SortablePlugin) Stores() []plugin.AlpineStore {
    return []plugin.AlpineStore{
        {
            Name: "sortable",
            InitialState: map[string]any{
                "items": []any{},
            },
            Methods: `
                add(item) {
                    this.items.push(item);
                },
                remove(id) {
                    this.items = this.items.filter(i => i.id !== id);
                }
            `,
        },
    }
}

Collecting Alpine assets:

registry := plugin.NewRegistry()
registry.Use(sortableplugin.New())
registry.Initialize(ctx)

// Collect all Alpine assets
scripts := registry.CollectScripts()        // All external scripts
directives := registry.CollectDirectives()  // All custom directives
stores := registry.CollectStores()          // All Alpine stores
magics := registry.CollectMagics()          // All magic properties
components := registry.CollectAlpineComponents() // All Alpine.data components
Theme Plugins

Theme plugins provide custom themes and fonts:

package corporatetheme

import (
    "github.com/xraph/forgeui/plugin"
    "github.com/xraph/forgeui/theme"
)

type CorporateThemePlugin struct {
    *plugin.ThemePluginBase
}

func New() *CorporateThemePlugin {
    themes := map[string]theme.Theme{
        "corporate-light": {
            Colors: theme.ColorTokens{
                Primary:   "#003366",
                Secondary: "#0066CC",
                // ... more colors
            },
            Radius: theme.RadiusTokens{
                Default: "0.25rem",
                // ... more radii
            },
        },
        "corporate-dark": {
            Colors: theme.ColorTokens{
                Primary:   "#0066CC",
                Secondary: "#003366",
                // ... more colors
            },
            Radius: theme.RadiusTokens{
                Default: "0.25rem",
            },
        },
    }

    fonts := []theme.Font{
        {Family: "Inter", Weights: []int{400, 600, 700}},
        {Family: "JetBrains Mono", Weights: []int{400, 500}},
    }

    return &CorporateThemePlugin{
        ThemePluginBase: plugin.NewThemePluginBaseWithFonts(
            plugin.PluginInfo{
                Name:    "corporate-theme",
                Version: "1.0.0",
            },
            themes,
            "corporate-light",
            fonts,
        ),
    }
}

func (p *CorporateThemePlugin) CSS() string {
    return `
        :root {
            --corporate-accent: #FF6600;
        }
        .corporate-button {
            background: var(--corporate-accent);
        }
    `
}
Middleware Plugins

Middleware plugins provide HTTP middleware with priority-based execution:

package htmxplugin

import (
    "context"
    "net/http"
    "github.com/xraph/forgeui/plugin"
)

type HTMXPlugin struct {
    *plugin.MiddlewarePluginBase
}

func New() *HTMXPlugin {
    middleware := func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // Detect HTMX requests
            if r.Header.Get("HX-Request") == "true" {
                ctx := context.WithValue(r.Context(), htmxRequestKey, true)
                r = r.WithContext(ctx)
            }
            next.ServeHTTP(w, r)
        })
    }

    return &HTMXPlugin{
        MiddlewarePluginBase: plugin.NewMiddlewarePluginBase(
            plugin.PluginInfo{
                Name:    "htmx",
                Version: "1.0.0",
            },
            middleware,
            10, // Low priority = executes early
        ),
    }
}

Using middleware plugins:

registry := plugin.NewRegistry()
registry.Use(htmxplugin.New())
registry.Initialize(ctx)

// Collect middleware in priority order
middlewares := registry.CollectMiddleware()

// Chain middleware
handler := yourHandler
for i := len(middlewares) - 1; i >= 0; i-- {
    handler = middlewares[i].Middleware()(handler)
}
Multi-Type Plugins

A plugin can implement multiple interfaces:

type SuperPlugin struct {
    *plugin.PluginBase
}

// Implements ComponentPlugin
func (p *SuperPlugin) Components() map[string]plugin.ComponentConstructor {
    return map[string]plugin.ComponentConstructor{
        "SuperComponent": superComponentConstructor,
    }
}

func (p *SuperPlugin) CVAExtensions() map[string]*forgeui.CVA {
    return nil
}

// Implements AlpinePlugin
func (p *SuperPlugin) Scripts() []plugin.Script {
    return []plugin.Script{
        {Name: "super-script", URL: "https://example.com/super.js"},
    }
}

func (p *SuperPlugin) Directives() []plugin.AlpineDirective {
    return []plugin.AlpineDirective{
        {Name: "super", Definition: "(el) => {}"},
    }
}

// ... implement other AlpinePlugin methods

// Implements ThemePlugin
func (p *SuperPlugin) Themes() map[string]theme.Theme {
    return map[string]theme.Theme{
        "super": theme.DefaultLight(),
    }
}

// ... implement other ThemePlugin methods

Advanced Usage

Type-Specific Retrieval
// Get specific plugin types
if cp, ok := registry.GetComponentPlugin("charts"); ok {
    components := cp.Components()
}

if ap, ok := registry.GetAlpinePlugin("sortable"); ok {
    scripts := ap.Scripts()
}

if tp, ok := registry.GetThemePlugin("corporate-theme"); ok {
    themes := tp.Themes()
}
Accessing Other Plugins

Plugins can access other plugins through the registry:

func (p *MyPlugin) Init(ctx context.Context, r *plugin.Registry) error {
    // Get another plugin
    if other, ok := r.Get("other-plugin"); ok {
        // Use the other plugin
    }
    
    return nil
}
Hook Context

Hooks receive a context with useful information:

r.Hooks().On(plugin.HookBeforeRender, func(hctx *plugin.HookContext) error {
    // Access context
    ctx := hctx.Context
    
    // Access data
    pluginName := hctx.Data["plugin"].(string)
    
    // Access nodes (for render hooks)
    nodes := hctx.Nodes
    
    return nil
})

Best Practices

  1. Keep plugins focused: Each plugin should do one thing well
  2. Document dependencies: Clearly document what your plugin depends on
  3. Handle errors gracefully: Return clear error messages from Init/Shutdown
  4. Clean up resources: Always clean up in Shutdown
  5. Use hooks sparingly: Only hook into events you actually need
  6. Test thoroughly: Write tests for your plugin's functionality
  7. Version carefully: Follow semantic versioning for your plugins

Testing

Example test for a plugin:

func TestMyPlugin(t *testing.T) {
    ctx := context.Background()
    registry := plugin.NewRegistry()
    
    p := New()
    if err := registry.Register(p); err != nil {
        t.Fatalf("Register failed: %v", err)
    }
    
    if err := registry.Initialize(ctx); err != nil {
        t.Fatalf("Initialize failed: %v", err)
    }
    defer registry.Shutdown(ctx)
    
    // Test your plugin functionality...
}

API Reference

Plugin Interface
type Plugin interface {
    Name() string
    Version() string
    Description() string
    Dependencies() []Dependency
    Init(ctx context.Context, registry *Registry) error
    Shutdown(ctx context.Context) error
}
Registry Methods

Basic Operations:

  • NewRegistry() *Registry - Create a new registry
  • Register(p Plugin) error - Register a plugin
  • Get(name string) (Plugin, bool) - Get a plugin by name
  • Has(name string) bool - Check if a plugin exists
  • All() []Plugin - Get all plugins
  • Count() int - Get plugin count
  • Use(plugins ...Plugin) *Registry - Register multiple plugins (chainable)
  • Unregister(name string) error - Remove a plugin
  • Initialize(ctx context.Context) error - Initialize all plugins
  • Shutdown(ctx context.Context) error - Shutdown all plugins
  • Hooks() *HookManager - Get the hook manager

Type-Specific Retrieval:

  • GetComponentPlugin(name string) (ComponentPlugin, bool) - Get a component plugin
  • GetAlpinePlugin(name string) (AlpinePlugin, bool) - Get an Alpine plugin
  • GetThemePlugin(name string) (ThemePlugin, bool) - Get a theme plugin

Asset Collection:

  • CollectComponents() map[string]ComponentConstructor - Collect all component constructors
  • CollectScripts() []Script - Collect all scripts (sorted by priority)
  • CollectDirectives() []AlpineDirective - Collect all Alpine directives
  • CollectStores() []AlpineStore - Collect all Alpine stores
  • CollectMagics() []AlpineMagic - Collect all Alpine magic properties
  • CollectAlpineComponents() []AlpineComponent - Collect all Alpine.data components
  • CollectMiddleware() []MiddlewarePlugin - Collect all middleware (sorted by priority)
HookManager Methods
  • On(hook string, fn HookFunc) - Register a hook handler
  • Off(hook string) - Remove all handlers for a hook
  • Trigger(hook string, ctx *HookContext) error - Execute hook handlers
  • Has(hook string) bool - Check if a hook has handlers
  • Count(hook string) int - Get handler count for a hook

Examples

See the example/ directory for complete examples of:

  • Creating custom plugins
  • Using dependencies
  • Registering hooks
  • Plugin discovery

Contributing

Contributions are welcome! Please ensure:

  • All tests pass
  • Code coverage remains above 80%
  • Documentation is updated
  • Examples are provided for new features

Documentation

Overview

Package plugin provides the ForgeUI plugin system.

Plugins extend ForgeUI functionality through a common interface. The registry manages plugin lifecycle, dependencies, and hooks.

Basic usage:

registry := plugin.NewRegistry()
registry.Use(
    myplugin.New(),
    anotherplugin.New(),
)

if err := registry.Initialize(ctx); err != nil {
    log.Fatal(err)
}
defer registry.Shutdown(ctx)

Index

Constants

View Source
const (
	HookBeforeInit     = "before_init"
	HookAfterInit      = "after_init"
	HookBeforeShutdown = "before_shutdown"
	HookAfterShutdown  = "after_shutdown"
	HookBeforeRender   = "before_render"
	HookAfterRender    = "after_render"
	HookBeforeHead     = "before_head"
	HookAfterHead      = "after_head"
	HookBeforeBody     = "before_body"
	HookAfterBody      = "after_body"
	HookBeforeScripts  = "before_scripts"
	HookAfterScripts   = "after_scripts"
)

Hook names for plugin lifecycle and rendering.

Available hooks:

Lifecycle hooks:

  • before_init: Before plugin initialization
  • after_init: After plugin initialization
  • before_shutdown: Before plugin shutdown
  • after_shutdown: After plugin shutdown

Render hooks:

  • before_render: Before page render
  • after_render: After page render
  • before_head: Before <head> content
  • after_head: After <head> content
  • before_body: Before <body> content
  • after_body: After </body> (scripts area)
  • before_scripts: Before script tags
  • after_scripts: After script tags

Variables

This section is empty.

Functions

This section is empty.

Types

type AlpineComponent

type AlpineComponent struct {
	// Name is the component name (used with x-data="name")
	Name string

	// Definition is the JavaScript function returning the component state
	// Signature: (...args) => ({ state, methods })
	Definition string
}

AlpineComponent represents an Alpine.data component.

Components encapsulate reactive state and methods that can be reused across the application.

Example:

AlpineComponent{
    Name: "dropdown",
    Definition: `
        () => ({
            open: false,
            toggle() {
                this.open = !this.open;
            },
            close() {
                this.open = false;
            }
        })
    `,
}

type AlpineDirective

type AlpineDirective struct {
	// Name is the directive name (used as x-name)
	Name string

	// Definition is the JavaScript function implementing the directive
	// Signature: (el, { expression, modifiers }, { evaluate, effect, cleanup }) => {}
	Definition string
}

AlpineDirective represents a custom Alpine directive.

Directives extend Alpine's x- attribute system. The definition is JavaScript code that Alpine will execute.

Example:

AlpineDirective{
    Name: "sortable",
    Definition: `
        (el, { expression, modifiers }, { evaluate }) => {
            let options = expression ? evaluate(expression) : {};
            new Sortable(el, options);
        }
    `,
}

type AlpineMagic

type AlpineMagic struct {
	// Name is the magic property name (accessed via $name)
	Name string

	// Definition is the JavaScript function that returns the magic value
	// Signature: (el, { Alpine }) => value
	Definition string
}

AlpineMagic represents a custom magic property.

Magic properties are accessed via $name and can return any value.

Example:

AlpineMagic{
    Name: "clipboard",
    Definition: `
        (el) => {
            return {
                copy(text) {
                    navigator.clipboard.writeText(text);
                }
            }
        }
    `,
}

type AlpinePlugin

type AlpinePlugin interface {
	Plugin

	// Scripts returns external scripts to load (libraries, dependencies).
	Scripts() []Script

	// Directives returns custom Alpine directives.
	Directives() []AlpineDirective

	// Stores returns Alpine stores to register globally.
	Stores() []AlpineStore

	// Magics returns custom magic properties.
	Magics() []AlpineMagic

	// AlpineComponents returns Alpine.data components.
	AlpineComponents() []AlpineComponent
}

AlpinePlugin extends Alpine.js functionality.

An AlpinePlugin can provide:

  • External scripts/libraries to load
  • Custom Alpine directives (e.g., x-sortable)
  • Global Alpine stores for state management
  • Magic properties (e.g., $myPlugin)
  • Alpine.data components

Example:

type SortablePlugin struct {
    *PluginBase
}

func (p *SortablePlugin) Scripts() []Script {
    return []Script{
        {Name: "sortablejs", URL: "https://cdn.jsdelivr.net/.../Sortable.min.js"},
    }
}

func (p *SortablePlugin) Directives() []AlpineDirective {
    return []AlpineDirective{
        {Name: "sortable", Definition: "..."},
    }
}

type AlpinePluginBase

type AlpinePluginBase struct {
	*PluginBase
}

AlpinePluginBase provides default implementations for AlpinePlugin. Embed this to implement only the methods you need.

func NewAlpinePluginBase

func NewAlpinePluginBase(info PluginInfo) *AlpinePluginBase

NewAlpinePluginBase creates a new AlpinePluginBase.

func (*AlpinePluginBase) AlpineComponents

func (a *AlpinePluginBase) AlpineComponents() []AlpineComponent

AlpineComponents returns an empty slice by default.

func (*AlpinePluginBase) Directives

func (a *AlpinePluginBase) Directives() []AlpineDirective

Directives returns an empty slice by default.

func (*AlpinePluginBase) Magics

func (a *AlpinePluginBase) Magics() []AlpineMagic

Magics returns an empty slice by default.

func (*AlpinePluginBase) Scripts

func (a *AlpinePluginBase) Scripts() []Script

Scripts returns an empty slice by default.

func (*AlpinePluginBase) Stores

func (a *AlpinePluginBase) Stores() []AlpineStore

Stores returns an empty slice by default.

type AlpineStore

type AlpineStore struct {
	// Name is the store identifier (accessed via $store.name)
	Name string

	// InitialState is the initial state as a map
	InitialState map[string]any

	// Methods is JavaScript code defining store methods
	// These are merged with the initial state
	Methods string
}

AlpineStore represents a global Alpine store.

Stores provide reactive state accessible via $store.storeName. The initial state and methods are combined into a single object.

Example:

AlpineStore{
    Name: "notifications",
    InitialState: map[string]any{
        "items": []any{},
        "count": 0,
    },
    Methods: `
        add(item) {
            this.items.push(item);
            this.count++;
        },
        remove(id) {
            this.items = this.items.filter(i => i.id !== id);
            this.count--;
        }
    `,
}

type ComponentConstructor

type ComponentConstructor func(props any, children ...g.Node) g.Node

ComponentConstructor is a function that creates a component. It receives props (any type) and optional children nodes.

Example:

func lineChartConstructor(props any, children ...g.Node) g.Node {
    opts := props.(*ChartOptions)
    return html.Div(
        html.Class("chart-container"),
        g.Attr("data-chart-type", "line"),
        g.Attr("data-chart-data", opts.DataJSON()),
        g.Group(children),
    )
}

type ComponentPlugin

type ComponentPlugin interface {
	Plugin

	// Components returns a map of component names to their constructors.
	// Component names should be CamelCase and unique.
	Components() map[string]ComponentConstructor

	// CVAExtensions returns CVA configurations for component variants.
	// The keys should match component names from Components().
	CVAExtensions() map[string]*forgeui.CVA
}

ComponentPlugin extends ForgeUI with new UI components.

A ComponentPlugin provides:

  • Component constructors for creating new components
  • Optional CVA extensions for variant styling

Example:

type ChartPlugin struct {
    *ComponentPluginBase
}

func NewChartPlugin() *ChartPlugin {
    return &ChartPlugin{
        ComponentPluginBase: NewComponentPluginBase(
            PluginInfo{Name: "charts", Version: "1.0.0"},
            map[string]ComponentConstructor{
                "LineChart": lineChartConstructor,
                "BarChart":  barChartConstructor,
            },
        ),
    }
}

type ComponentPluginBase

type ComponentPluginBase struct {
	*PluginBase
	// contains filtered or unexported fields
}

ComponentPluginBase provides a base implementation for component plugins. Embed this in your plugin to inherit default behavior.

Example:

type MyPlugin struct {
    *ComponentPluginBase
}

func NewComponentPluginBase

func NewComponentPluginBase(info PluginInfo, components map[string]ComponentConstructor) *ComponentPluginBase

NewComponentPluginBase creates a new ComponentPluginBase.

func NewComponentPluginBaseWithCVA

func NewComponentPluginBaseWithCVA(
	info PluginInfo,
	components map[string]ComponentConstructor,
	cva map[string]*forgeui.CVA,
) *ComponentPluginBase

NewComponentPluginBaseWithCVA creates a ComponentPluginBase with CVA extensions.

func (*ComponentPluginBase) AddCVA

func (c *ComponentPluginBase) AddCVA(name string, cva *forgeui.CVA)

AddCVA adds a CVA configuration for a component.

func (*ComponentPluginBase) AddComponent

func (c *ComponentPluginBase) AddComponent(name string, constructor ComponentConstructor)

AddComponent adds a component constructor to the plugin. This can be called during plugin initialization.

func (*ComponentPluginBase) CVAExtensions

func (c *ComponentPluginBase) CVAExtensions() map[string]*forgeui.CVA

CVAExtensions returns the CVA configurations.

func (*ComponentPluginBase) Components

func (c *ComponentPluginBase) Components() map[string]ComponentConstructor

Components returns the component constructors.

type Dependency

type Dependency struct {
	Name     string
	Version  string // semver constraint, e.g., ">=1.0.0"
	Optional bool
}

Dependency represents a plugin dependency with version constraints.

func (Dependency) Satisfies

func (d Dependency) Satisfies(version string) bool

Satisfies checks if a version satisfies the constraint. Supports: =, >=, <=, >, <, ~, ^

type HookContext

type HookContext struct {
	Context context.Context //nolint:containedctx // Context is intentionally passed to hook handlers
	Data    map[string]any
	Nodes   []g.Node // For render hooks
}

HookContext provides context to hook handlers.

type HookFunc

type HookFunc func(ctx *HookContext) error

HookFunc is a hook handler function.

type HookManager

type HookManager struct {
	// contains filtered or unexported fields
}

HookManager manages hook registration and execution.

func NewHookManager

func NewHookManager() *HookManager

NewHookManager creates a new hook manager.

func (*HookManager) Count

func (m *HookManager) Count(hook string) int

Count returns the number of handlers for a hook.

func (*HookManager) Has

func (m *HookManager) Has(hook string) bool

Has checks if a hook has any handlers registered.

func (*HookManager) Off

func (m *HookManager) Off(hook string)

Off removes all handlers for a hook.

func (*HookManager) On

func (m *HookManager) On(hook string, fn HookFunc)

On registers a hook handler.

func (*HookManager) Trigger

func (m *HookManager) Trigger(hook string, ctx *HookContext) error

Trigger executes all handlers for a hook. Returns the first error encountered.

type MiddlewarePlugin

type MiddlewarePlugin interface {
	Plugin

	// Middleware returns the HTTP middleware function.
	// The middleware should call next.ServeHTTP(w, r) to continue the chain.
	Middleware() func(http.Handler) http.Handler

	// Priority determines execution order.
	// Lower values execute first (e.g., 1 = first, 100 = last).
	// Default priority if not specified: 50.
	Priority() int
}

MiddlewarePlugin provides HTTP middleware for the application.

Middleware plugins can intercept and modify HTTP requests/responses. They are executed in priority order (lower priority = executes first).

Example:

type HTMXPlugin struct {
    *PluginBase
}

func (p *HTMXPlugin) Middleware() func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // Detect HTMX requests
            if r.Header.Get("HX-Request") == "true" {
                ctx := context.WithValue(r.Context(), htmxRequestKey, true)
                r = r.WithContext(ctx)
            }
            next.ServeHTTP(w, r)
        })
    }
}

func (p *HTMXPlugin) Priority() int {
    return 10 // Execute early
}

type MiddlewarePluginBase

type MiddlewarePluginBase struct {
	*PluginBase
	// contains filtered or unexported fields
}

MiddlewarePluginBase provides a base implementation for middleware plugins.

func NewMiddlewarePluginBase

func NewMiddlewarePluginBase(
	info PluginInfo,
	middleware func(http.Handler) http.Handler,
	priority int,
) *MiddlewarePluginBase

NewMiddlewarePluginBase creates a new MiddlewarePluginBase.

func (*MiddlewarePluginBase) Middleware

func (m *MiddlewarePluginBase) Middleware() func(http.Handler) http.Handler

Middleware returns the middleware function.

func (*MiddlewarePluginBase) Priority

func (m *MiddlewarePluginBase) Priority() int

Priority returns the execution priority.

type Plugin

type Plugin interface {
	// Name returns the unique plugin identifier
	Name() string

	// Version returns the plugin version (semver)
	Version() string

	// Description returns a human-readable description
	Description() string

	// Dependencies returns required plugin dependencies
	Dependencies() []Dependency

	// Init initializes the plugin
	Init(ctx context.Context, registry *Registry) error

	// Shutdown cleanly shuts down the plugin
	Shutdown(ctx context.Context) error
}

Plugin is the base interface all plugins must implement.

type PluginBase

type PluginBase struct {
	// contains filtered or unexported fields
}

PluginBase provides a base implementation for plugins. Embed this in custom plugins to inherit default implementations.

func NewPluginBase

func NewPluginBase(info PluginInfo) *PluginBase

NewPluginBase creates a new PluginBase with the given info.

func (*PluginBase) Dependencies

func (p *PluginBase) Dependencies() []Dependency

Dependencies returns the plugin dependencies.

func (*PluginBase) Description

func (p *PluginBase) Description() string

Description returns the plugin description.

func (*PluginBase) Init

func (p *PluginBase) Init(ctx context.Context, r *Registry) error

Init is the default implementation (override in plugins).

func (*PluginBase) Name

func (p *PluginBase) Name() string

Name returns the plugin name.

func (*PluginBase) Shutdown

func (p *PluginBase) Shutdown(ctx context.Context) error

Shutdown is the default implementation (override in plugins).

func (*PluginBase) Version

func (p *PluginBase) Version() string

Version returns the plugin version.

type PluginInfo

type PluginInfo struct {
	Name         string
	Version      string
	Description  string
	Author       string
	License      string
	Homepage     string
	Repository   string
	Tags         []string
	Dependencies []Dependency
}

PluginInfo contains plugin metadata.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry manages plugin registration and lifecycle.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates a new plugin registry.

func (*Registry) All

func (r *Registry) All() []Plugin

All returns all registered plugins.

func (*Registry) CollectAlpineComponents

func (r *Registry) CollectAlpineComponents() []AlpineComponent

CollectAlpineComponents collects all Alpine.data components from Alpine plugins.

func (*Registry) CollectComponents

func (r *Registry) CollectComponents() map[string]ComponentConstructor

CollectComponents collects all component constructors from component plugins. Returns a map of component name to constructor.

func (*Registry) CollectDirectives

func (r *Registry) CollectDirectives() []AlpineDirective

CollectDirectives collects all Alpine directives from Alpine plugins.

func (*Registry) CollectMagics

func (r *Registry) CollectMagics() []AlpineMagic

CollectMagics collects all Alpine magic properties from Alpine plugins.

func (*Registry) CollectMiddleware

func (r *Registry) CollectMiddleware() []MiddlewarePlugin

CollectMiddleware returns all middleware plugins in priority order. Middleware is already sorted by priority in the registry.

func (*Registry) CollectScripts

func (r *Registry) CollectScripts() []Script

CollectScripts collects all scripts from Alpine plugins. Scripts are returned in priority order (lower priority first).

func (*Registry) CollectStores

func (r *Registry) CollectStores() []AlpineStore

CollectStores collects all Alpine stores from Alpine plugins.

func (*Registry) Count

func (r *Registry) Count() int

Count returns the number of registered plugins.

func (*Registry) Discover

func (r *Registry) Discover(dir string) error

Discover loads plugins from a directory (Go plugins).

Note: This is optional and experimental. Go plugins have significant limitations:

  • Only supported on Linux, FreeBSD, and macOS
  • Must be built with the same Go version as the main program
  • Must use the same versions of all dependencies
  • Cannot be unloaded once loaded

For production use, consider statically linking plugins instead.

Plugin files must:

  • Have a .so extension
  • Export a variable named "Plugin" of type plugin.Plugin

Example plugin:

package main

import "github.com/xraph/forgeui/plugin"

var Plugin = &MyPlugin{}

type MyPlugin struct {
    plugin.PluginBase
}

func (*Registry) DiscoverSafe

func (r *Registry) DiscoverSafe(dir string) []error

DiscoverSafe is like Discover but continues on error, collecting all errors. Returns all errors encountered during discovery.

func (*Registry) Get

func (r *Registry) Get(name string) (Plugin, bool)

Get retrieves a plugin by name.

func (*Registry) GetAlpinePlugin

func (r *Registry) GetAlpinePlugin(name string) (AlpinePlugin, bool)

GetAlpinePlugin retrieves an Alpine plugin by name.

func (*Registry) GetComponentPlugin

func (r *Registry) GetComponentPlugin(name string) (ComponentPlugin, bool)

GetComponentPlugin retrieves a component plugin by name.

func (*Registry) GetThemePlugin

func (r *Registry) GetThemePlugin(name string) (ThemePlugin, bool)

GetThemePlugin retrieves a theme plugin by name.

func (*Registry) Has

func (r *Registry) Has(name string) bool

Has checks if a plugin is registered.

func (*Registry) Hooks

func (r *Registry) Hooks() *HookManager

Hooks returns the hook manager for this registry.

func (*Registry) Initialize

func (r *Registry) Initialize(ctx context.Context) error

Initialize initializes all plugins in dependency order.

The initialization process: 1. Resolves all dependencies 2. Performs topological sort to determine order 3. Initializes each plugin in order 4. Triggers before_init and after_init hooks

If any plugin fails to initialize, the process stops and returns an error.

func (*Registry) Register

func (r *Registry) Register(p Plugin) error

Register adds a plugin to the registry. Automatically detects plugin type and stores in appropriate registry.

func (*Registry) ResolveDependencies

func (r *Registry) ResolveDependencies() error

ResolveDependencies checks all dependencies are satisfied.

func (*Registry) Shutdown

func (r *Registry) Shutdown(ctx context.Context) error

Shutdown shuts down all plugins in reverse initialization order.

Unlike initialization, shutdown attempts to shut down all plugins even if some fail. All errors are collected and returned as a single error.

This ensures that resources are cleaned up as much as possible.

func (*Registry) TopologicalSort

func (r *Registry) TopologicalSort() ([]Plugin, error)

TopologicalSort returns plugins in dependency order using Kahn's algorithm. Returns an error if a circular dependency is detected.

func (*Registry) Unregister

func (r *Registry) Unregister(name string) error

Unregister removes a plugin.

func (*Registry) Use

func (r *Registry) Use(plugins ...Plugin) *Registry

Use is a convenience method for chaining registrations.

type Script

type Script struct {
	// Name is a unique identifier for the script
	Name string

	// URL is the script source (https://... or /path/to/script.js)
	URL string

	// Inline is JavaScript code to execute (alternative to URL)
	Inline string

	// Defer adds the defer attribute (loads after HTML parsing)
	Defer bool

	// Async adds the async attribute (loads asynchronously)
	Async bool

	// Priority determines load order (lower = loads first)
	// Default: 50. Use 1-10 for critical dependencies.
	Priority int

	// Module adds type="module" attribute
	Module bool

	// Integrity is the SRI hash for the script
	Integrity string

	// Crossorigin sets the crossorigin attribute
	Crossorigin string
}

Script represents an external script to load.

Scripts can be loaded from URLs or inlined. Priority determines load order (lower values load first).

Example:

Script{
    Name:     "chartjs",
    URL:      "https://cdn.jsdelivr.net/npm/chart.js",
    Priority: 10,
    Defer:    true,
}

type ThemePlugin

type ThemePlugin interface {
	Plugin

	// Themes returns a map of theme names to Theme configurations.
	// Theme names should be unique and descriptive (e.g., "corporate-light").
	Themes() map[string]theme.Theme

	// DefaultTheme returns the name of the default theme to use.
	// This should match one of the keys from Themes().
	// Return empty string to let the system choose.
	DefaultTheme() string

	// CSS returns additional CSS to inject.
	// This can include custom properties, utility classes, or font-face rules.
	CSS() string

	// Fonts returns fonts to load.
	// These will be loaded automatically via Google Fonts or custom URLs.
	Fonts() []theme.Font
}

ThemePlugin provides custom themes for ForgeUI.

A ThemePlugin can provide:

  • Multiple theme presets (light/dark variants)
  • Additional CSS for custom properties or utilities
  • Custom fonts to load

Example:

type CorporateThemePlugin struct {
    *PluginBase
}

func (p *CorporateThemePlugin) Themes() map[string]theme.Theme {
    return map[string]theme.Theme{
        "corporate-light": corporateLightTheme,
        "corporate-dark":  corporateDarkTheme,
    }
}

func (p *CorporateThemePlugin) Fonts() []theme.Font {
    return []theme.Font{
        {Family: "Inter", Weights: []int{400, 600, 700}},
    }
}

type ThemePluginBase

type ThemePluginBase struct {
	*PluginBase
	// contains filtered or unexported fields
}

ThemePluginBase provides default implementations for ThemePlugin. Embed this to implement only the methods you need.

func NewThemePluginBase

func NewThemePluginBase(
	info PluginInfo,
	themes map[string]theme.Theme,
	defaultTheme string,
) *ThemePluginBase

NewThemePluginBase creates a new ThemePluginBase.

func NewThemePluginBaseWithFonts

func NewThemePluginBaseWithFonts(
	info PluginInfo,
	themes map[string]theme.Theme,
	defaultTheme string,
	fonts []theme.Font,
) *ThemePluginBase

NewThemePluginBaseWithFonts creates a ThemePluginBase with fonts.

func (*ThemePluginBase) AddFont

func (t *ThemePluginBase) AddFont(font theme.Font)

AddFont adds a font to the plugin.

func (*ThemePluginBase) AddTheme

func (t *ThemePluginBase) AddTheme(name string, th theme.Theme)

AddTheme adds a theme to the plugin. This can be called during plugin initialization.

func (*ThemePluginBase) CSS

func (t *ThemePluginBase) CSS() string

CSS returns empty string by default. Override this method to provide custom CSS.

func (*ThemePluginBase) DefaultTheme

func (t *ThemePluginBase) DefaultTheme() string

DefaultTheme returns the default theme name.

func (*ThemePluginBase) Fonts

func (t *ThemePluginBase) Fonts() []theme.Font

Fonts returns the font configurations.

func (*ThemePluginBase) Themes

func (t *ThemePluginBase) Themes() map[string]theme.Theme

Themes returns the theme configurations.

type Version

type Version struct {
	Major int
	Minor int
	Patch int
}

Version represents a semantic version.

func ParseVersion

func ParseVersion(s string) (*Version, error)

ParseVersion parses a semantic version string.

func (*Version) Compare

func (v *Version) Compare(other *Version) int

Compare compares two versions. Returns: -1 if v < other, 0 if v == other, 1 if v > other

func (*Version) String

func (v *Version) String() string

String returns the version as a string.

type VersionConstraint

type VersionConstraint struct {
	Operator string
	Version  *Version
}

VersionConstraint represents a version constraint.

func ParseConstraint

func ParseConstraint(constraint string) (*VersionConstraint, error)

ParseConstraint parses a version constraint string.

func (*VersionConstraint) Check

func (c *VersionConstraint) Check(version *Version) bool

Check checks if a version satisfies the constraint.

func (*VersionConstraint) String

func (c *VersionConstraint) String() string

String returns the constraint as a string.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL