Uniview

@uniview/runtime

Plugin-side runtime for bootstrapping React plugins in Web Workers and server environments

The runtime package bootstraps React plugins in isolated environments (Web Workers, Node.js, Deno, Bun).

Installation

pnpm add @uniview/runtime

Quick Start

Web Worker Plugin

The simplest way to create a worker-based plugin:

// worker.ts
import { startWorkerPlugin } from "@uniview/runtime";
import App from "./App";

startWorkerPlugin({ App });

That's it! The runtime handles all RPC communication with the host.

Your React Component

Write a normal React component - it works exactly as expected:

// App.tsx
import { useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);

  return (
    <div className="p-4">
      <p>Count: {count}</p>
      <button onClick={() => setCount((c) => c + 1)}>Increment</button>
    </div>
  );
}

API

startWorkerPlugin

Bootstrap a plugin in a Web Worker with automatic RPC setup:

function startWorkerPlugin(options: { App: React.ComponentType }): void;

This function:

  1. Creates an RPC channel using the Worker's postMessage
  2. Exposes the HostToPluginAPI methods to the host
  3. Sets up the React reconciler
  4. Starts rendering when initialize() is called

createPluginRuntime

For advanced use cases, create a runtime with custom transport:

import { createPluginRuntime } from "@uniview/runtime";
import type { PluginToHostAPI } from "@uniview/protocol";

interface PluginRuntimeOptions {
  App: React.ComponentType;
  hostApi: PluginToHostAPI;
  onInitialize?: (props: JSONValue) => void;
  onUpdateProps?: (props: JSONValue) => void;
}

interface PluginRuntime {
  start(): void;
  stop(): void;
  executeHandler(handlerId: string, args: JSONValue[]): Promise<void>;
}

const runtime = createPluginRuntime({
  App: MyApp,
  hostApi: {
    updateTree: (tree) => sendToHost(tree),
    log: (level, args) => console.log(level, ...args),
  },
});

runtime.start();

Entry Points

The package provides multiple entry points for different environments:

EntryImportEnvironment
Default@uniview/runtimeWeb Worker, Main thread
WebSocket Client@uniview/runtime/ws-clientNode.js, Deno, Bun (connects to Bridge)
WebSocket Server@uniview/runtime/ws-serverNode.js, Deno, Bun (Deprecated)

For server-side plugins that connect to a Bridge server:

// server-plugin.ts
import { connectToHostServer } from "@uniview/runtime/ws-client";
import App from "./App";

connectToHostServer({
  App,
  serverUrl: "ws://localhost:3000", // Bridge server URL
  pluginId: "my-plugin", // Unique plugin identifier
});

Options:

OptionTypeDescription
AppReact.ComponentTypeYour React plugin component
serverUrlstringBridge server WebSocket URL
pluginIdstringUnique identifier for this plugin
initialPropsJSONValueOptional initial props

The plugin connects to {serverUrl}/plugins/{pluginId} and waits for a host to connect.

WebSocket Server Plugin

Deprecated: Running plugins as WebSocket servers is deprecated. Use @uniview/runtime/ws-client to connect to a Bridge server instead. This approach simplifies deployment and port management.

For server-side plugins that communicate over WebSocket:

// server-plugin.ts
import { startWSServerPlugin } from "@uniview/runtime/ws-server";
import App from "./App";

startWSServerPlugin({
  App,
  port: 3001,
});

Plugin Lifecycle

Host calls initialize(props)


┌─────────────────────────┐
│  Runtime creates:       │
│  - React reconciler     │
│  - Handler registry     │
│  - Tree serializer      │
└───────────┬─────────────┘


┌─────────────────────────┐
│  React renders App      │
│  with initial props     │
└───────────┬─────────────┘


┌─────────────────────────┐
│  Tree serialized to     │
│  UINode + handler IDs   │
└───────────┬─────────────┘


    hostApi.updateTree()

When events occur:

Host calls executeHandler(id, args)


┌─────────────────────────┐
│  Registry finds handler │
│  by ID and executes it  │
└───────────┬─────────────┘


┌─────────────────────────┐
│  React state updates    │
│  trigger re-render      │
└───────────┬─────────────┘


    hostApi.updateTree()

Building Plugins

Bundle your plugin for worker execution:

// tsdown.config.ts
import { defineConfig } from "tsdown";

export default defineConfig({
  entry: ["src/worker.ts"],
  format: ["esm"],
  platform: "browser",
  outDir: "dist",
  clean: true,
});

Plugins run in isolated environments. Do not use window, document, or other browser-only APIs.

Environment Restrictions

Since plugins run in Web Workers or server environments:

AllowedNot Allowed
React hooksDOM APIs (document, window)
fetch()localStorage
console.log()Direct DOM manipulation
Async/awaitBrowser-specific APIs
Web Worker APIsNode.js APIs (in worker mode)

Handler Execution

Event handlers are serialized as IDs and executed via RPC:

// In your plugin
<button onClick={() => setCount(c => c + 1)}>Click</button>

// Becomes UINode:
{
  type: "button",
  props: {
    _onClickHandlerId: "handler_abc123"
  },
  children: ["Click"]
}

The runtime maintains a HandlerRegistry that maps IDs back to the original functions.

On this page