Plugin Architecture

How UsketchPlugin, PluginContext, and the lifecycle work together.

Overview

In uSketch v2, every feature is a plugin. The core framework provides registries and services; plugins register shapes, tools, layers, commands, and event handlers during setup.

The Plugin Interface

type PluginTeardown = () => void | Promise<void>;

interface UsketchPlugin {
  readonly id: string;
  readonly name: string;
  setup(ctx: PluginContext): PluginTeardown | void | Promise<PluginTeardown | void>;
}
FieldDescription
idUnique identifier, e.g. "usketch-plugin-shape-rect"
nameHuman-readable display name
setup(ctx)Called once during app initialization. Register everything here. Return a teardown function if there is anything to clean up; return void (or omit a return) if not.

The former plugin.teardown property has been removed. Cleanup is expressed as the function returned from setup. Stashing the cleanup on this is unsafe under React StrictMode (a second setup call silently overwrites the first instance’s teardown closure), so always return it from setup — each createApp call owns its own closure.

PluginContext

The ctx argument passed to setup() provides access to all registries:

interface PluginContext {
  store: BoardStore;          // State management
  layers: LayerManager;       // Rendering layer registration
  tools: ToolRegistry;        // Tool registration
  shapes: ShapeRegistry;      // Shape type registration
  commands: CommandRegistry;   // Undo/redo command execution
  shortcuts: ShortcutRegistry; // Keyboard shortcut registration
  events: EventBus;           // Inter-plugin communication
  transient: TransientRegistry; // Ephemeral objects (cursors, effects)
  lod: LodController;         // Level-of-detail (render mode) control
  ui: UiRegistry;             // UI extension registration (e.g. selection foreground)
  externalContent: ExternalContentRegistry; // Handling dropped/pasted external content
  actions: ActionRegistry;    // Declarative plugin operations; powers the Control HUD
  services: ServiceRegistry;  // String-keyed registry for plugin-provided services (IoC)
}

Lifecycle

createApp({ store, plugins })

  ├─ 1. Register plugins in PluginRegistry

  ├─ 2. Call setup(ctx) for each plugin (in order)
  │     ├─ Register layers
  │     ├─ Register tools
  │     ├─ Register shapes
  │     ├─ Register commands
  │     ├─ Register shortcuts
  │     └─ Subscribe to events

  ├─ 3. Mount the React tree

  └─ [On destroy]
      └─ Call the teardown returned by setup in LIFO order (reverse of registration)

Plugins are initialized sequentially in the order they appear in the plugins array. This means a plugin can depend on registrations made by an earlier plugin.

Self-Contained Plugins

A shape plugin typically bundles both the shape definition and the drawing tool in a single package. For example, the rectangle plugin registers:

  • A shape type ("rectangle") in ShapeRegistry
  • A drawing tool ("rectangle-draw") in ToolRegistry

This keeps the concept of “rectangle” self-contained in one package.

Naming Conventions

CategoryPackage patternExample
Tool pluginusketch-plugin-tool-{name}usketch-plugin-tool-select
Shape pluginusketch-plugin-shape-{name}usketch-plugin-shape-rect
Background pluginusketch-plugin-bg-{name}usketch-plugin-bg-grid
Feature pluginusketch-plugin-{name}usketch-plugin-snap