A universal plugin system for React and Solid plugins that render in any host framework.
Uniview enables writing plugins in React or Solid that can be rendered by Svelte, Vue, React, or any other framework. Plugins run in isolated environments (Web Workers, Node.js, Deno, Bun) and communicate with hosts via RPC.
RPC (kkrpc)
┌───────────────────────┐ ◄──────────────────► ┌──────────────────┐
│ Plugin (React/Solid) │ UINode tree │ Host (Svelte) │
│ Web Worker │ │ or Vue, React │
└───────────────────────┘ └──────────────────┘
Key Features:
| Package | Description |
|---|---|
| @uniview/protocol | Core types, UINode schema, RPC interfaces |
| @uniview/react-renderer | Custom React reconciler producing UINode trees |
| @uniview/solid-renderer | Solid universal renderer producing UINode trees |
| @uniview/react-runtime | React plugin bootstrap for Worker/WebSocket |
| @uniview/solid-runtime | Solid plugin bootstrap for Worker/WebSocket |
| @uniview/host-sdk | Framework-agnostic host controller |
| @uniview/host-svelte | Svelte 5 rendering adapter |
| @uniview/tui-renderer | Terminal UI renderer (non-DOM, like React Native) |
The fastest way to see Uniview in action:
Web Example (Svelte Host):
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Run the complete example (bridge + plugins + host)
cd examples/host-svelte-demo
pnpm dev:all
Then open http://localhost:5173 and try both Worker and Node.js modes.
Benchmark Mode:
Select "Benchmark" demo to compare full-tree vs incremental update performance:
cd examples/host-svelte-demo
pnpm dev:all
# Open http://localhost:5173?demo=benchmark&update=incremental
The benchmark starts with 1000 items (max 2000) and shows:
Solid Plugin Example:
# Build Solid plugins (requires Babel transform)
cd examples/plugin-solid-example && bun run build.ts
# Run with bridge + Solid plugins + Svelte host
cd examples/host-svelte-demo
pnpm dev:all:solid
Open http://localhost:5173, select "Solid" in the framework selector.
Terminal UI Example:
cd examples/tui-demo
pnpm dev
Renders React plugins directly to terminal (no browser, no DOM).
Native macOS Example (SwiftUI):
# Terminal 1: Start bridge
cd examples/bridge-server && bun src/index.ts
# Terminal 2: Start plugin
cd examples/plugin-example && bun src/simple-demo.client.ts
# Open Xcode project and run
cd examples/host-macos-demo
open HostMacOSDemo.xcodeproj
# Press Cmd+R in Xcode
Renders React plugins as native SwiftUI app.
Native macOS Example (AppKit — diff-based reconciliation):
# Terminal 1: Start bridge
cd examples/bridge-server && bun src/index.ts
# Terminal 2: Start plugin
cd examples/plugin-example && bun src/simple-demo.client.ts
# Open Xcode project and run
cd examples/host-appkit-demo
open HostAppKitDemo.xcodeproj
# Press Cmd+R in Xcode
Same React plugins rendered as native AppKit views with a view model layer and id-based tree reconciler for efficient in-place updates.
Create a React component and bootstrap it with the runtime:
// worker.ts
import { startWorkerPlugin } from "@uniview/react-runtime";
import App from "./App";
startWorkerPlugin({ App });
// 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>
);
}
Or write plugins in Solid with the same pattern:
// worker.ts
import { startSolidWorkerPlugin } from "@uniview/solid-runtime";
import App from "./App";
startSolidWorkerPlugin({ App });
// App.tsx
import { createSignal } from "solid-js";
const App = () => {
const [count, setCount] = createSignal(0);
return (
<div className="p-4">
<p>Count: {count()}</p>
<Button onClick={() => setCount((c) => c + 1)} title="Increment" />
</div>
);
};
export default App;
Solid plugins require a build step (Babel + babel-preset-solid with generate: "universal") since the universal JSX transform can't run directly in Bun/Node. See examples/plugin-solid-example/build.ts for the build configuration.
Load and render the plugin:
<script lang="ts">
import { PluginHost } from '@uniview/host-svelte';
import { createWorkerController, createComponentRegistry } from '@uniview/host-sdk';
const registry = createComponentRegistry();
const controller = createWorkerController({
pluginUrl: '/plugins/my-plugin.js'
});
</script>
<PluginHost {controller} {registry}>
{#snippet loading()}
<p>Loading plugin...</p>
{/snippet}
</PluginHost>
┌─────────────────┐ ┌─────────────────┐
│ Browser Host │ ◄─postMessage─►│ Web Worker │
│ (Svelte) │ │ (Plugin) │
└─────────────────┘ └─────────────────┘
For server-side plugins, Uniview uses a Bridge Server pattern:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Browser Host │◄─────►│ Bridge Server │◄─────►│ Plugin Client │
│ (Svelte) │ WS │ (Elysia) │ WS │ (Node.js) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
:5173 :3000 (connects to bridge)
Why Bridge Architecture?
:3000), simplifying deployment.// Plugin connects as client
import { connectToHostServer } from "@uniview/react-runtime/ws-client";
connectToHostServer({
App: MyPlugin,
serverUrl: "ws://localhost:3000",
pluginId: "my-plugin",
});
// Host connects to same bridge
const controller = createWebSocketController({
serverUrl: "ws://localhost:3000",
pluginId: "my-plugin",
});
Uniview supports two update strategies for sending UI changes from plugin to host:
| Mode | Description | Best For |
|---|---|---|
| Full Tree | Sends entire UINode tree on every render | Small trees, simple apps |
| Incremental | Sends only mutations (append, remove, update) | Large lists, frequent updates |
Incremental mode tracks individual mutations during the render phase:
appendChild - New nodes added to parentremoveChild - Nodes removed from parentinsertBefore - Nodes inserted at specific positionsetText - Text content changedsetProps - Props changed on existing node// Plugin side - enable incremental mode
startWorkerPlugin({
App: MyApp,
mode: "incremental", // or "full" (default)
});
Performance comparison (1000 items):
See Benchmark section for detailed metrics.
| Mode | Environment | Isolation | Use Case |
|---|---|---|---|
| Worker | Browser | Full sandbox | Production, untrusted plugins |
| WebSocket | Node.js/Deno/Bun | Process boundary | Server-side, full runtime access |
| Main Thread | Browser | None | Development only |
// Worker mode (production)
const controller = createWorkerController({
pluginUrl: "/plugins/my-plugin.js",
});
// WebSocket mode (server-side plugins)
const controller = createWebSocketController({
serverUrl: "ws://localhost:3000",
pluginId: "my-plugin",
});
// Main thread mode (development)
import App from "./plugin/App";
const controller = createMainController({ App });
uniview/
├── packages/
│ ├── protocol/ # Shared types and contracts
│ ├── react-renderer/ # Custom React reconciler
│ ├── solid-renderer/ # Solid universal renderer
│ ├── react-runtime/ # React plugin bootstrap (worker + ws-client)
│ ├── solid-runtime/ # Solid plugin bootstrap (worker + ws-client)
│ ├── host-sdk/ # Host controller logic
│ ├── host-svelte/ # Svelte 5 adapter
│ └── tui-renderer/ # Terminal UI renderer (non-DOM)
├── examples/
│ ├── host-svelte-demo/ # Web example (Svelte + Bridge)
│ ├── host-macos-demo/ # Native macOS app (SwiftUI)
│ ├── host-appkit-demo/ # Native macOS app (AppKit, diff-based)
│ ├── host-react-demo/ # React host example
│ ├── host-vue-demo/ # Vue host example
│ ├── tui-demo/ # Terminal UI example
│ ├── bridge-server/ # WebSocket bridge server
│ ├── plugin-api/ # Reusable React components
│ ├── plugin-solid-api/ # Reusable Solid components
│ ├── plugin-example/ # React example plugins
│ └── plugin-solid-example/ # Solid example plugins
├── vendors/
│ └── kkrpc/ # RPC library (submodule)
└── docs/ # Documentation site
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Run the full example
cd examples/host-svelte-demo
pnpm dev:all # Starts bridge + plugins + host
# Or run components separately:
# Terminal 1: Bridge server
bun server/index.ts
# Terminal 2: Plugin clients
cd ../plugin-example && pnpm client
# Terminal 3: SvelteKit host
pnpm dev
See the docs folder or visit the documentation site for:
Plugin (React or Solid)
│
▼
┌─────────────────────┐
│ react-renderer / │ Custom reconciler converts
│ solid-renderer │ components to in-memory tree
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ serializeTree() │ Converts to JSON-safe UINode,
│ HandlerRegistry │ replaces functions with IDs
└──────────┬──────────┘
│
▼ RPC (kkrpc)
┌─────────────────────┐
│ host-sdk │ PluginController manages
│ PluginController │ connection and tree updates
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ host-svelte │ ComponentRenderer recursively
│ ComponentRenderer │ renders UINode as Svelte
└─────────────────────┘
Uniview plugins render on any target that implements the UINode protocol:
| Target | Example | Rendering Approach |
|---|---|---|
| Svelte (Web) | host-svelte-demo |
Svelte 5 ComponentRenderer |
| React (Web) | host-react-demo |
React component tree |
| Vue (Web) | host-vue-demo |
Vue component tree |
| SwiftUI (macOS) | host-macos-demo |
Declarative SwiftUI views |
| AppKit (macOS) | host-appkit-demo |
Imperative NSViews with diff reconciler |
| Terminal | tui-demo |
ANSI escape codes (standalone) |
The AppKit demo uses a view model layer with dirty-tracking bitfields and a tree reconciler that matches nodes by stable ID for O(1) diffing — the same architecture used by React Native. See examples/host-appkit-demo/README.md for a full design guide on building this kind of system.
MIT