|
1 | 1 | # Tauri Demo with kkrpc |
2 | 2 |
|
3 | | -To run this demo, you need to build their entire monorepo. |
| 3 | +A Tauri desktop application demonstrating bidirectional RPC between a Svelte frontend and Bun/Deno/Node.js sidecar processes using kkrpc. This demo shows how kkrpc replaces Electron's contentBridge pattern in Tauri apps. |
4 | 4 |
|
5 | | -Run in the root directory of the monorepo. |
| 5 | +## Overview |
| 6 | + |
| 7 | +This demo features a code editor that can execute JavaScript/TypeScript code in three different runtimes (Bun, Deno, Node.js) through sidecar processes. The frontend communicates with these processes via stdio (stdin/stdout) using kkrpc's type-safe RPC channel. |
| 8 | + |
| 9 | +### Key Features |
| 10 | + |
| 11 | +- **Multi-runtime support**: Execute code in Bun, Deno, or Node.js |
| 12 | +- **Live code editor**: Monaco-based editor with syntax highlighting |
| 13 | +- **Real-time output**: stdout/stderr display for each execution |
| 14 | +- **Type-safe RPC**: Full TypeScript inference across process boundaries |
| 15 | +- **ES Module support**: Import and use runtime-specific APIs (e.g., `bun:sqlite`, Deno KV) |
| 16 | + |
| 17 | +## Prerequisites |
| 18 | + |
| 19 | +- **Bun** (latest version) |
| 20 | +- **Deno** (latest version) |
| 21 | +- **Node.js** (v18+) |
| 22 | +- **Rust** toolchain (for Tauri) |
| 23 | +- **pnpm** package manager |
| 24 | + |
| 25 | +## Project Structure |
| 26 | + |
| 27 | +``` |
| 28 | +tauri-demo/ |
| 29 | +├── src/ |
| 30 | +│ ├── backend/ # Sidecar implementations |
| 31 | +│ │ ├── api.ts # Shared API definition (eval, etc.) |
| 32 | +│ │ ├── bun.ts # Bun runtime entry point |
| 33 | +│ │ └── node.ts # Node.js runtime entry point |
| 34 | +│ ├── lib/ |
| 35 | +│ │ └── components/ # Svelte UI components |
| 36 | +│ ├── routes/ # SvelteKit routes |
| 37 | +│ │ ├── +page.svelte # Main demo page |
| 38 | +│ │ └── examples/ # Additional examples (editor, math) |
| 39 | +│ └── sample-script/ # Test scripts for each runtime |
| 40 | +├── src-tauri/ # Rust Tauri application |
| 41 | +│ ├── src/ |
| 42 | +│ │ ├── main.rs # Tauri entry point |
| 43 | +│ │ └── lib.rs # Tauri commands |
| 44 | +│ └── binaries/ # Compiled sidecar binaries (auto-generated) |
| 45 | +└── build.ts # Build script for sidecar binaries |
| 46 | +``` |
| 47 | + |
| 48 | +## Quick Start |
| 49 | + |
| 50 | +### 1. Build the Monorepo |
| 51 | + |
| 52 | +First, build the entire kkrpc monorepo from the root directory: |
6 | 53 |
|
7 | 54 | ```bash |
| 55 | +# From repository root |
| 56 | +cd /path/to/kkrpc |
8 | 57 | pnpm install |
9 | 58 | pnpm build |
10 | 59 | ``` |
11 | 60 |
|
12 | | -Then run in this directory. |
| 61 | +### 2. Build Sidecar Binaries |
| 62 | + |
| 63 | +The demo requires compiled binaries for each runtime. Build them with: |
| 64 | + |
| 65 | +```bash |
| 66 | +cd examples/tauri-demo |
| 67 | +bun run build |
| 68 | +``` |
| 69 | + |
| 70 | +This will: |
| 71 | + |
| 72 | +- Compile the Deno backend (from `../deno-backend/`) |
| 73 | +- Bundle and package the Node.js backend using `pkg` |
| 74 | +- Compile the Bun backend using `bun build --compile` |
| 75 | +- Generate binaries in `src-tauri/binaries/` |
| 76 | + |
| 77 | +**Note**: The first build may take several minutes as `pkg` downloads Node.js binaries. |
| 78 | + |
| 79 | +### 3. Run the Tauri App |
13 | 80 |
|
14 | 81 | ```bash |
15 | 82 | pnpm tauri dev |
16 | 83 | ``` |
| 84 | + |
| 85 | +This starts the Tauri development server with hot reload. |
| 86 | + |
| 87 | +## Usage |
| 88 | + |
| 89 | +### Code Editor |
| 90 | + |
| 91 | +1. **Select Runtime**: Choose between Bun, Deno, or Node.js from the dropdown |
| 92 | +2. **Write Code**: Enter JavaScript/TypeScript in the editor |
| 93 | +3. **Run**: Click the "Run" button to execute in the selected runtime |
| 94 | +4. **View Output**: Check stdout/stderr panels for results |
| 95 | + |
| 96 | +### Sample Scripts |
| 97 | + |
| 98 | +Each runtime has a default sample script demonstrating unique features: |
| 99 | + |
| 100 | +- **Deno**: Uses Deno KV for key-value storage |
| 101 | +- **Bun**: Demonstrates `bun:sqlite` for in-memory databases |
| 102 | +- **Node.js**: Shows Node.js built-in modules (os, crypto, perf_hooks) |
| 103 | + |
| 104 | +### Custom Code Examples |
| 105 | + |
| 106 | +**Basic console.log:** |
| 107 | + |
| 108 | +```javascript |
| 109 | +console.log("Hello from the sidecar!") |
| 110 | +``` |
| 111 | + |
| 112 | +**Bun with SQLite:** |
| 113 | + |
| 114 | +```javascript |
| 115 | +import { Database } from "bun:sqlite" |
| 116 | + |
| 117 | +const db = new Database(":memory:") |
| 118 | +db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)") |
| 119 | +db.run("INSERT INTO users (name) VALUES ('Alice')") |
| 120 | +const result = db.query("SELECT * FROM users").all() |
| 121 | +console.log(result) |
| 122 | +``` |
| 123 | + |
| 124 | +**Deno with KV:** |
| 125 | + |
| 126 | +```javascript |
| 127 | +const kv = await Deno.openKv() |
| 128 | +await kv.set(["user", "1"], { name: "Alice", age: 30 }) |
| 129 | +const entry = await kv.get(["user", "1"]) |
| 130 | +console.log(entry.value) |
| 131 | +``` |
| 132 | + |
| 133 | +**Node.js with Crypto:** |
| 134 | + |
| 135 | +```javascript |
| 136 | +const { createHash } = require("crypto") |
| 137 | +const hash = createHash("sha256") |
| 138 | +hash.update("Hello World") |
| 139 | +console.log(hash.digest("hex")) |
| 140 | +``` |
| 141 | + |
| 142 | +## Architecture |
| 143 | + |
| 144 | +### How It Works |
| 145 | + |
| 146 | +``` |
| 147 | +┌─────────────────┐ Tauri Shell API ┌─────────────────┐ |
| 148 | +│ Svelte UI │◄───────────────────────►│ Sidecar Proc │ |
| 149 | +│ (WebView) │ stdin/stdout (stdio) │ (Bun/Node/Deno)│ |
| 150 | +└─────────────────┘ └─────────────────┘ |
| 151 | + │ |
| 152 | + │ Commands (spawn/kill) |
| 153 | + ▼ |
| 154 | +┌─────────────────┐ |
| 155 | +│ Rust Main │ |
| 156 | +│ (Tauri Core) │ |
| 157 | +└─────────────────┘ |
| 158 | +``` |
| 159 | + |
| 160 | +1. **Frontend** spawns a sidecar process via Tauri's shell API |
| 161 | +2. **kkrpc** establishes bidirectional communication over stdio |
| 162 | +3. **Frontend** calls `api.eval(code)` to execute code remotely |
| 163 | +4. **Sidecar** executes the code and returns results via RPC |
| 164 | +5. **stdout/stderr** are streamed back to the frontend for display |
| 165 | + |
| 166 | +### Key Components |
| 167 | + |
| 168 | +**TauriShellStdio Adapter** (`src/routes/+page.svelte`): |
| 169 | + |
| 170 | +```typescript |
| 171 | +import { Command } from "@tauri-apps/plugin-shell" |
| 172 | +import { RPCChannel, TauriShellStdio } from "kkrpc/browser" |
| 173 | + |
| 174 | +const cmd = Command.sidecar(`binaries/${runtime}`) |
| 175 | +const process = await cmd.spawn() |
| 176 | +const stdio = new TauriShellStdio(cmd.stdout, process) |
| 177 | +const rpc = new RPCChannel(stdio, {}) |
| 178 | +const api = rpc.getAPI() |
| 179 | + |
| 180 | +// Execute code remotely |
| 181 | +await api.eval(code) |
| 182 | +``` |
| 183 | + |
| 184 | +**API Definition** (`src/backend/api.ts`): |
| 185 | + |
| 186 | +```typescript |
| 187 | +export class Api { |
| 188 | + async eval(code: string) { |
| 189 | + // Dynamic import with base64 encoding for ES module support |
| 190 | + const base64 = Buffer.from(code).toString("base64") |
| 191 | + const dataUrl = `data:text/javascript;base64,${base64}` |
| 192 | + return await import(dataUrl) |
| 193 | + } |
| 194 | +} |
| 195 | +``` |
| 196 | + |
| 197 | +## Known Issues & Limitations |
| 198 | + |
| 199 | +### Bun on macOS |
| 200 | + |
| 201 | +Bun has a known issue with stdin on macOS that prevents kkrpc from working properly. The demo will show a warning when Bun is selected on macOS. |
| 202 | + |
| 203 | +**Workaround**: Use Node.js or Deno on macOS. |
| 204 | + |
| 205 | +**Reference**: https://github.com/kunkunsh/kkrpc/issues/11 |
| 206 | + |
| 207 | +### Binary Sizes |
| 208 | + |
| 209 | +Compiled binaries are approximately: |
| 210 | + |
| 211 | +- **Bun**: ~57MB |
| 212 | +- **Deno**: ~70MB |
| 213 | +- **Node.js**: ~67MB |
| 214 | + |
| 215 | +These are bundled into the Tauri app and contribute to the overall app size. |
| 216 | + |
| 217 | +## Development |
| 218 | + |
| 219 | +### Rebuilding Sidecars |
| 220 | + |
| 221 | +If you modify the backend code (`src/backend/*.ts`), rebuild the binaries: |
| 222 | + |
| 223 | +```bash |
| 224 | +bun run build |
| 225 | +``` |
| 226 | + |
| 227 | +### Adding New Runtimes |
| 228 | + |
| 229 | +To add support for another runtime: |
| 230 | + |
| 231 | +1. Create a new backend entry file in `src/backend/` |
| 232 | +2. Implement the `Api` class with your methods |
| 233 | +3. Add the runtime to the build script (`build.ts`) |
| 234 | +4. Update the frontend runtime selector |
| 235 | + |
| 236 | +### Debugging |
| 237 | + |
| 238 | +Enable verbose logging: |
| 239 | + |
| 240 | +```bash |
| 241 | +# In the browser console (frontend) |
| 242 | +localStorage.debug = 'kkrpc:*' |
| 243 | + |
| 244 | +# For sidecar processes, check stderr output in the UI |
| 245 | +``` |
| 246 | + |
| 247 | +## Troubleshooting |
| 248 | + |
| 249 | +### "Error: UNEXPECTED-20" (Node.js binary) |
| 250 | + |
| 251 | +This error occurs when the Node.js binary wasn't built correctly with `pkg`. Ensure: |
| 252 | + |
| 253 | +- You're using `@yao-pkg/pkg` version 6.3.2 (pinned in package.json) |
| 254 | +- The build completed without errors |
| 255 | +- The binary in `src-tauri/binaries/` is executable |
| 256 | + |
| 257 | +### "Unexpected token '{'. import call expects..." (Bun) |
| 258 | + |
| 259 | +This happens when Bun's `eval()` receives ES module syntax. The backend now uses dynamic `import()` with base64 encoding to support imports. If you see this, the backend may not be updated - rebuild with `bun run build`. |
| 260 | + |
| 261 | +### kkrpc JSON appears in stdout |
| 262 | + |
| 263 | +This is expected behavior - both user `console.log` output and kkrpc messages go to stdout. The frontend RPC channel intercepts kkrpc messages automatically. |
| 264 | + |
| 265 | +## License |
| 266 | + |
| 267 | +MIT © kkrpc contributors |
0 commit comments