Uniview

Getting Started

Build your first Uniview plugin in 5 minutes

This guide walks you through creating a simple interactive plugin and rendering it in a Svelte host.

Prerequisites

  • Node.js 18+
  • pnpm (recommended) or npm

Installation

Install the core packages:

# For React plugin development
pnpm add @uniview/react-runtime @uniview/react-renderer @uniview/protocol react

# For Solid plugin development
pnpm add @uniview/solid-runtime @uniview/solid-renderer @uniview/protocol solid-js

# For host development (Svelte)
pnpm add @uniview/host-sdk @uniview/host-svelte @uniview/protocol

Creating a Plugin

Plugins can be written in React or Solid. This guide shows the React path — see below for Solid.

1. Write Your React Component

Create a React component. Plugins can use standard React hooks, state, and standard HTML elements.

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

export default function App() {
  const [name, setName] = useState("");
  const [submitted, setSubmitted] = useState(false);

  // Conditional rendering
  if (submitted) {
    return (
      <div className="p-4 border rounded-lg bg-green-50">
        <h1 className="text-xl font-bold text-green-700">Welcome, {name}!</h1>
        <p className="text-green-600 mt-2">Your registration was successful.</p>
        <button
          onClick={() => {
            setSubmitted(false);
            setName("");
          }}
          className="mt-4 px-4 py-2 bg-white border border-green-200 text-green-700 rounded hover:bg-green-50 transition-colors"
        >
          Reset
        </button>
      </div>
    );
  }

  return (
    <div className="p-4 border rounded-lg space-y-4 bg-white">
      <h1 className="text-xl font-bold">Registration Plugin</h1>

      <div className="flex flex-col gap-2">
        <label className="text-sm font-medium text-gray-700">
          Enter your name:
        </label>
        <input
          value={name}
          onChange={(e) => setName(e.target.value)}
          className="border p-2 rounded focus:ring-2 focus:ring-blue-500 outline-none"
          placeholder="John Doe"
        />
      </div>

      <button
        onClick={() => setSubmitted(true)}
        disabled={!name.trim()}
        className="w-full px-4 py-2 bg-blue-600 text-white rounded font-medium hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
      >
        Submit
      </button>
    </div>
  );
}

2. Create the Worker Entry Point

Bootstrap your plugin using the runtime:

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

startWorkerPlugin({ App });

3. Build the Plugin

Bundle for browser (Web Worker target):

build.ts
// Using Bun
await Bun.build({
  entrypoints: ["./src/worker.ts"],
  outdir: "./dist",
  target: "browser",
  format: "esm",
  minify: true,
});

Or with other bundlers (esbuild, Vite, etc.):

esbuild src/worker.ts --bundle --format=esm --outfile=dist/plugin.js

The plugin bundle must be self-contained with all dependencies included. Web Workers can't access the host's modules.

Creating a Solid Plugin

The same steps apply for Solid, with different packages and a required build step:

src/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)}>Increment</button>
    </div>
  );
};

export default App;
src/worker.ts
import { startSolidWorkerPlugin } from "@uniview/solid-runtime";
import App from "./App";

startSolidWorkerPlugin({ App });

Solid plugins require a Babel build step with babel-preset-solid (generate: "universal") because Bun/Node can't natively run Solid's JSX transform. See examples/plugin-solid-example/build.ts for the full build configuration.

When building Solid plugins for Bun/Node.js (target: "bun"), you must add conditions: ["browser"] to the build config. Without this, Bun resolves solid-js to its SSR build which has no reactivity.

Creating a Host (Svelte)

1. Set Up the Controller

src/routes/+page.svelte
<script lang="ts">
  import { PluginHost } from '@uniview/host-svelte';
  import { createWorkerController, createComponentRegistry } from '@uniview/host-sdk';
  import type { Component } from 'svelte';

  // Create component registry (optional: for custom components)
  const registry = createComponentRegistry<Component>();

  // Create controller pointing to your plugin
  const controller = createWorkerController({
    pluginUrl: '/plugins/plugin.js'
  });
</script>

<div class="p-8 max-w-2xl mx-auto">
  <h1 class="text-2xl font-bold mb-8">Uniview Host Demo</h1>

  <div class="border rounded-xl shadow-sm overflow-hidden">
    <PluginHost {controller} {registry}>
      {#snippet loading()}
        <div class="p-8 text-center text-gray-500 animate-pulse">
          Loading plugin environment...
        </div>
      {/snippet}
    </PluginHost>
  </div>
</div>

2. Serve the Plugin

Place your built plugin in static/plugins/ (for SvelteKit) or configure your dev server to serve it.

3. Run the App

pnpm dev

Your plugin should now render inside the host!

Custom Components

By default, plugins can use HTML elements (div, button, input, etc.). For custom components:

Plugin Side

Create wrapper components that emit custom types:

plugin-api/Button.tsx
import { createElement } from "react";

export function Button({ title, onClick, variant = "primary" }) {
  return createElement("Button", { title, onClick, variant });
}

Host Side

Register renderers for custom types:

components/PluginButton.svelte
<script lang="ts">
  import { Button } from '$lib/components/ui/button';

  let { title, onclick, variant }: Props = $props();
</script>

<Button {variant} on:click={onclick}>
  {title}
</Button>
// In your page
registry.register("Button", PluginButton);

Runtime Modes

Uniview supports three runtime modes:

ModeUse CaseSecurity
WorkerProduction, untrusted pluginsSandboxed
WebSocketServer-side plugins, Node.js APIsNetwork isolated
Main ThreadDevelopment, debuggingNone

For complex setups involving multiple modes, check out the Advanced Host Guide.

Next Steps

On this page