UNPKG

@deckyfx/dioxus-react-bridge

Version:

React hooks and components for Dioxus IPC communication - lightweight bridge between React and Rust

667 lines (503 loc) 17.7 kB
# dioxus-react-bridge React hooks and components for seamless IPC communication with Dioxus Rust backend. [![npm version](https://img.shields.io/npm/v/dioxus-react-bridge.svg)](https://www.npmjs.com/package/dioxus-react-bridge) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) ## Features -**HTTP-like IPC API** - Request/response pattern with methods, URLs, headers, bodies -**Event Streaming** - Real-time events from Rust → React with pub/sub pattern -**Streaming Tasks** - Progress tracking and chunked data transfer for long-running operations -**Zero Runtime Dependencies** - Lightweight EventEmitter replaces RxJS (~50-80KB saved) -**TypeScript First** - Full type safety with comprehensive type definitions -**Global Type Definitions** - Complete types for all Dioxus-injected APIs (`window.dioxusBridge`, `window.ipc`, etc.) -**Build Utility** - Configurable build tool with Tailwind support, watch mode, and auto-copy to Dioxus assets -**React Hooks** - Idiomatic React patterns with hooks for all IPC operations -**Platform Agnostic** - Works on desktop (webview), web (WASM), and mobile ## Installation ```bash # Using bun bun add dioxus-react-bridge react react-dom # Using npm npm install dioxus-react-bridge react react-dom # Using yarn yarn add dioxus-react-bridge react react-dom ``` ## Quick Start ### 1. TypeScript Setup (Recommended) Import global type definitions in your entry file for full TypeScript support of Dioxus-injected APIs: ```tsx // src/index.tsx or src/main.tsx import '@deckyfx/dioxus-react-bridge/global'; ``` This provides complete type definitions for: - `window.dioxusBridge` - Main IPC bridge - `window.ipc` - Low-level IPC channel - `window.interpreter` - Dioxus virtual DOM interpreter - `window.showDXToast()`, `window.scheduleDXToast()`, `window.closeDXToast()` - Toast notifications **Alternative: Add to tsconfig.json** ```json { "compilerOptions": { "types": ["@deckyfx/dioxus-react-bridge/global"] } } ``` ### 2. Wrap Your App ```tsx import { IPCReady, RustIPCProvider } from '@deckyfx/dioxus-react-bridge'; import '@deckyfx/dioxus-react-bridge/global'; // TypeScript types function App() { return ( <IPCReady> <RustIPCProvider> <MyApp /> </RustIPCProvider> </IPCReady> ); } ``` ### 3. Make IPC Calls ```tsx import { useRustIPC } from '@deckyfx/dioxus-react-bridge'; function Calculator() { const { fetch } = useRustIPC(); const [result, setResult] = useState(null); const calculateFibonacci = async () => { const response = await fetch('ipc://calculator/fibonacci?number=10'); setResult(response.body.result); }; return ( <div> <button onClick={calculateFibonacci}>Calculate</button> {result && <div>Result: {result}</div>} </div> ); } ``` ### 4. Listen to Rust Events ```tsx import { useRustEventListener } from '@deckyfx/dioxus-react-bridge'; function HeartbeatMonitor() { const heartbeat = useRustEventListener<{ count: number }>('rust:heartbeat'); return <div>Heartbeat: {heartbeat?.count || 'Waiting...'}</div>; } ``` ### 5. Direct Access to Dioxus APIs (with Types!) ```tsx // Thanks to global types, you get full IntelliSense and type checking async function loadAsset() { // window.dioxusBridge is fully typed const response = await window.dioxusBridge.fetch('ipc://assets/image'); console.log(response.body); } function showNotification() { // Toast functions are fully typed window.showDXToast?.('Success', 'Operation completed!', 'success', 3000); } ``` ## Complete Workflow Guide ### Step 1: Create Dioxus App ```bash dx new my-app cd my-app ``` ### Step 2: Add Rust Crates Add to `Cargo.toml`: ```toml [dependencies] dioxus = "0.7.0" dioxus-ipc-bridge = { path = "../dioxus-ipc-bridge" } dioxus-ipc-bridge-macros = { path = "../dioxus-ipc-bridge-macros" } dioxus-react-integration = { path = "../dioxus-react-integration" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" ``` ### Step 3: Set Up Rust Side ```rust use dioxus::prelude::*; use dioxus_ipc_bridge::prelude::*; fn main() { // Initialize IPC bridge let bridge = IpcBridge::builder() .timeout(std::time::Duration::from_secs(30)) .build(); bridge.initialize(); // Launch Dioxus app dioxus::launch(app); } fn app() -> Element { rsx! { // Your Dioxus UI ReactContainer { bundle: asset!("/assets/react/bundle.js"), mount_id: "react-root" } } } ``` ### Step 4: Create React App ```bash # Initialize bun project mkdir react-app && cd react-app bun init # Install dependencies bun add react react-dom dioxus-react-bridge # Install dev dependencies bun add -d @types/react @types/react-dom typescript ``` ### Step 5: Write React App Create `src/index.tsx`: ```tsx import React, { useState } from 'react'; import { createRoot } from 'react-dom/client'; import { IPCReady, RustIPCProvider, useRustIPC } from '@deckyfx/dioxus-react-bridge'; import '@deckyfx/dioxus-react-bridge/global'; // Import global types function App() { const { fetch } = useRustIPC(); const [result, setResult] = useState(null); const handleClick = async () => { const response = await fetch('ipc://endpoint'); setResult(response.body); }; return ( <div> <button onClick={handleClick}>Call Rust</button> {result && <div>{JSON.stringify(result)}</div>} </div> ); } const root = document.getElementById('react-root'); if (root) { createRoot(root).render( <IPCReady> <RustIPCProvider> <App /> </RustIPCProvider> </IPCReady> ); } ``` ### Step 6: Build React Bundle Create `build.ts` using the built-in build utility: ```typescript import { buildForDioxus } from '@deckyfx/dioxus-react-bridge/build'; await buildForDioxus({ // Entry point for your React app entrypoint: './src/index.tsx', // Where to output the bundle outdir: './dist', // Path to your Dioxus app's assets directory dioxusAssetsPath: '../my-dioxus-app/assets/react', // Minify the output minify: true, // Optional: Tailwind CSS processing tailwind: { input: './src/App.css', output: './src/App.processed.css', }, // Optional: Verbose logging verbose: true, }); ``` **Minimal Configuration:** ```typescript import { buildForDioxus } from '@deckyfx/dioxus-react-bridge/build'; // Just specify where to copy the bundle await buildForDioxus({ dioxusAssetsPath: '../my-dioxus-app/assets/react' }); ``` Run build: ```bash bun run build.ts ``` **Development Watch Mode:** Create `watch.ts` for automatic rebuilding during development: ```typescript import { watchForDioxus } from '@deckyfx/dioxus-react-bridge/build'; await watchForDioxus({ dioxusAssetsPath: '../my-dioxus-app/assets/react', verbose: true }); ``` Run watch mode: ```bash bun run watch.ts ``` ### Step 7: Run Your App The build utility automatically copies the bundle to your Dioxus assets directory (if `dioxusAssetsPath` is specified). If you didn't use `dioxusAssetsPath`, manually copy: ```bash cp react-app/dist/bundle.js ../my-app/assets/react/bundle.js ``` ### Step 8: Start Dioxus ```bash cd my-app dx serve ``` ## API Reference ### Build Utility The package includes a configurable build utility to simplify bundling React apps for Dioxus. #### `buildForDioxus(config?)` Main build function that handles Tailwind CSS processing, bundling, and copying to Dioxus assets. ```typescript import { buildForDioxus } from '@deckyfx/dioxus-react-bridge/build'; await buildForDioxus({ entrypoint: './src/index.tsx', outdir: './dist', outputFilename: 'bundle.js', dioxusAssetsPath: '../my-dioxus-app/assets/react', minify: true, tailwind: { input: './src/App.css', output: './src/App.processed.css', options: '--minify' }, loaders: { '.png': 'file', '.svg': 'file' }, verbose: true }); ``` **Configuration Options:** | Option | Type | Default | Description | |--------|------|---------|-------------| | `entrypoint` | `string` | `'./src/index.tsx'` | Entry point for your React app | | `outdir` | `string` | `'./dist'` | Output directory | | `outputFilename` | `string` | `'bundle.js'` | Output filename | | `dioxusAssetsPath` | `string` | `undefined` | Path to Dioxus assets directory | | `minify` | `boolean` | `true` | Enable minification | | `target` | `string` | `'browser'` | Build target (`'browser'`, `'bun'`, `'node'`) | | `tailwind` | `object` | `undefined` | Tailwind CSS configuration | | `tailwind.input` | `string` | `'./src/App.css'` | Tailwind input file | | `tailwind.output` | `string` | `'./src/App.processed.css'` | Tailwind output file | | `tailwind.options` | `string` | `'--minify'` | Tailwind CLI options | | `loaders` | `object` | See defaults | Custom file loaders | | `external` | `string[]` | `[]` | Packages to exclude from bundle | | `define` | `object` | `{}` | Environment variables to define | | `customBunConfig` | `object` | `{}` | Custom Bun build configuration | | `verbose` | `boolean` | `false` | Enable verbose logging | **Default Loaders:** - `.css``'text'` (inlined as string) - `.svg`, `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp``'file'` (base64 encoded) - `.woff`, `.woff2`, `.ttf`, `.otf`, `.eot``'file'` (base64 encoded) #### `watchForDioxus(config?)` Watch mode for automatic rebuilding during development. ```typescript import { watchForDioxus } from '@deckyfx/dioxus-react-bridge/build'; await watchForDioxus({ dioxusAssetsPath: '../my-dioxus-app/assets/react', verbose: true }); ``` **Features:** - Watches all files in `src/` directory recursively - Automatic rebuild on file changes - Ignores `node_modules` and processed files - Initial build on start #### `createBuildConfig(config)` Create a reusable build configuration with defaults applied. ```typescript import { createBuildConfig, buildForDioxus } from '@deckyfx/dioxus-react-bridge/build'; const myConfig = createBuildConfig({ dioxusAssetsPath: '../my-app/assets/react', minify: process.env.NODE_ENV === 'production', verbose: process.env.DEBUG === 'true' }); await buildForDioxus(myConfig); ``` ### Components #### `<IPCReady>` Suspense-like component that waits for `window.dioxusBridge` to be initialized. ```tsx <IPCReady fallback={<Spinner />} onReady={() => console.log('IPC ready!')} timeout={10000} > <App /> </IPCReady> ``` **Props:** - `fallback?: ReactNode` - Custom loading UI - `onReady?: () => void` - Callback when IPC is ready - `timeout?: number` - Timeout in ms (default: 10000) #### `<RustIPCProvider>` Context provider for IPC functionality. ```tsx <RustIPCProvider> <App /> </RustIPCProvider> ``` ### Hooks #### `useRustIPC()` Main hook for HTTP-like IPC requests. ```tsx const { fetch, isReady } = useRustIPC(); // GET request const response = await fetch('ipc://endpoint'); // POST request const response = await fetch('ipc://endpoint', { method: 'POST', body: { key: 'value' } }); ``` **Returns:** - `fetch<T>(url, options?)` - HTTP-like fetch function - `isReady: boolean` - Whether IPC is ready #### `useRustEventListener(channel)` Listen to events from Rust. ```tsx const heartbeat = useRustEventListener<{ count: number }>('rust:heartbeat'); ``` **Parameters:** - `channel: string` - Event channel name **Returns:** - `T | null` - Latest event data #### `useRustEventEmitter(channel)` Emit events to a channel. ```tsx const emit = useRustEventEmitter<{ message: string }>('notification'); emit({ message: 'Hello!' }); ``` **Parameters:** - `channel: string` - Event channel name **Returns:** - `(data: T) => void` - Emit function #### `useStreamingTask(options)` Manage long-running streaming tasks with progress tracking. ```tsx const { start, progress, result, isRunning, cancel } = useStreamingTask({ onProgress: (p) => console.log(`Progress: ${p.percent}%`), onComplete: (result) => console.log('Done!', result), }); // Start task await start('ipc://process/video', { method: 'POST', body: { videoId: '123' } }); ``` **Options:** - `onProgress?: (progress) => void` - `onChunk?: (chunk) => void` - `onComplete?: (result) => void` - `onError?: (error) => void` - `autoReassemble?: boolean` **Returns:** - `taskId: string | null` - `isRunning: boolean` - `progress: StreamingProgress | null` - `chunks: StreamingChunk[]` - `result: T | null` - `error: Error | null` - `start: (url, options) => Promise<void>` - `cancel: () => void` - `reset: () => void` ### Type Exports #### Main Types ```typescript import type { IpcFetchOptions, IpcResponse, IpcErrorResponse, StreamingProgress, StreamingChunk, StreamingTaskOptions, StreamingTaskState, DioxusBridge, IPCBridgeInstance, } from '@deckyfx/dioxus-react-bridge'; ``` #### Global Type Definitions Import global type definitions for complete TypeScript support of Dioxus-injected APIs: ```typescript // Import in your entry file import '@deckyfx/dioxus-react-bridge/global'; // Now you get full type safety for: window.dioxusBridge.fetch('ipc://endpoint'); // ✓ Fully typed window.ipc.postMessage(JSON.stringify({...})); // ✓ Fully typed window.showDXToast?.('Title', 'Message', 'success', 3000); // ✓ Fully typed window.interpreter?.scrollTo(id, options); // ✓ Fully typed (internal API) ``` **Available Global Types:** - **`window.dioxusBridge`** - Main IPC bridge with HTTP-like fetch API - `fetch<T>(url, options?)` - Make IPC requests - `rustEmit(channel, data)` - Emit events to React (called by Rust) - `IPCBridge` - Event system for pub/sub - `ipc.send(data)` - Low-level send - `ipc.hasIPCBridge()` - Check for event system - **`window.ipc`** - Low-level IPC channel - `postMessage(message)` - Send raw IPC message to Rust - **`window.interpreter`** - Dioxus virtual DOM interpreter (internal, advanced use only) - DOM manipulation: `scroll`, `scrollTo`, `setFocus`, `getClientRect` - Scroll getters: `getScrollHeight`, `getScrollLeft`, `getScrollTop`, `getScrollWidth` - Edit management: `enqueueBytes`, `flushQueuedBytes`, `rafEdits` - Drag & drop: `handleWindowsDragDrop`, `handleWindowsDragOver`, `handleWindowsDragLeave` - **`window.showDXToast()`** - Show toast notification - **`window.scheduleDXToast()`** - Schedule toast after reload - **`window.closeDXToast()`** - Close current toast **Example with Full Type Safety:** ```typescript import '@deckyfx/dioxus-react-bridge/global'; // All of these have full IntelliSense and type checking! async function example() { // Main IPC bridge const response = await window.dioxusBridge.fetch<{ result: number }>( 'ipc://calculator/fibonacci?number=10' ); console.log(response.body.result); // ✓ TypeScript knows this is a number // Toast notifications window.showDXToast?.('Success', 'Calculation complete!', 'success', 3000); // Event system window.dioxusBridge.IPCBridge?.on('rust:heartbeat').subscribe({ next: (data) => console.log(data) }); } ``` ## Architecture ``` ┌─────────────────────────────────────┐ │ React App (Your Code) │ │ - useRustIPC(), useRustEventListener │ └──────────────┬──────────────────────┘ │ ┌──────────────▼──────────────────────┐ │ dioxus-react-bridge (This Package)│ │ - HTTP-like fetch wrapper │ │ - Event system (EventBridge) │ │ - React hooks and components │ └──────────────┬──────────────────────┘ │ ┌──────────────▼──────────────────────┐ │ window.dioxusBridge (Injected) │ │ - Native Dioxus IPC channel │ │ - Injected by Rust before React │ └──────────────┬──────────────────────┘ │ ┌──────────────▼──────────────────────┐ │ Rust Backend (Dioxus + IPC Bridge)│ │ - Route handlers │ │ - Event emission │ │ - Business logic │ └─────────────────────────────────────┘ ``` ## Bundle Size This package has **ZERO runtime dependencies** except for React peer dependencies. - **RxJS removed**: Saved ~50-80KB (replaced with 2KB EventEmitter) - **Minified**: ~8KB - **Gzipped**: ~3KB ## Browser Support - Chrome/Edge 90+ - Firefox 88+ - Safari 14+ ## Contributing Contributions welcome! Please open an issue or PR. ## License MIT © 2025 dioxus-react-bridge contributors ## Related Projects - [Dioxus](https://github.com/DioxusLabs/dioxus) - Rust UI framework - [dioxus-ipc-bridge](https://github.com/your-username/dioxus-ipc-bridge) - Rust side of IPC bridge - [dioxus-react-integration](https://github.com/your-username/dioxus-react-integration) - React container component ## Support - GitHub Issues: [Report bugs](https://github.com/your-username/dioxus-react-bridge/issues) - Discussions: [Ask questions](https://github.com/your-username/dioxus-react-bridge/discussions)