@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/runtimeQuick 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:
- Creates an RPC channel using the Worker's
postMessage - Exposes the
HostToPluginAPImethods to the host - Sets up the React reconciler
- 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:
| Entry | Import | Environment |
|---|---|---|
| Default | @uniview/runtime | Web Worker, Main thread |
| WebSocket Client | @uniview/runtime/ws-client | Node.js, Deno, Bun (connects to Bridge) |
| WebSocket Server | @uniview/runtime/ws-server | Node.js, Deno, Bun (Deprecated) |
WebSocket Client Plugin (Recommended)
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:
| Option | Type | Description |
|---|---|---|
App | React.ComponentType | Your React plugin component |
serverUrl | string | Bridge server WebSocket URL |
pluginId | string | Unique identifier for this plugin |
initialProps | JSONValue | Optional 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:
| Allowed | Not Allowed |
|---|---|
| React hooks | DOM APIs (document, window) |
fetch() | localStorage |
console.log() | Direct DOM manipulation |
| Async/await | Browser-specific APIs |
| Web Worker APIs | Node.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.