@ic-reactor/vite-plugin
v0.1.0
Published
Vite plugin for zero-config IC reactor generation from Candid files
Maintainers
Readme
@ic-reactor/vite-plugin
Automatically generate type-safe React hooks for your Internet Computer canisters. This plugin watches your .did files and generates ready-to-use hooks with full TypeScript support.
Features
- ⚡ Zero Config — Point to your
.didfile and get hooks instantly - 🔄 Hot Reload — Automatically regenerates hooks when
.didfiles change - 📦 TypeScript Declarations — Full type safety with auto-generated types
- 🎯 Display Types — Optional
DisplayReactorsupport for React-friendly types - 🔌 Flexible — Works with any
ClientManagerconfiguration
Installation
# With npm
npm install -D @ic-reactor/vite-plugin
# With pnpm
pnpm add -D @ic-reactor/vite-plugin
# Required peer dependencies
npm install @ic-reactor/react @tanstack/react-query @icp-sdk/core
npm install -D @icp-sdk/bindgenQuick Start
1. Configure the Plugin
// vite.config.ts
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
import { icReactorPlugin } from "@ic-reactor/vite-plugin"
export default defineConfig({
plugins: [
react(),
icReactorPlugin({
canisters: [
{
name: "backend",
didFile: "./backend/backend.did",
},
],
}),
],
})2. Create Your ClientManager
The plugin expects you to have a ClientManager exported from a file. By default, it looks for ./src/lib/client.ts:
// src/lib/client.ts
import { ClientManager } from "@ic-reactor/react"
import { QueryClient } from "@tanstack/react-query"
export const queryClient = new QueryClient()
export const clientManager = new ClientManager({
queryClient,
withCanisterEnv: true, // Enable environment-based canister ID resolution
})3. Use Generated Hooks
The plugin generates hooks in ./src/canisters/<name>/index.ts:
// Generated at: ./src/canisters/backend/index.ts
import {
backendReactor,
useBackendQuery,
useBackendMutation,
useBackendSuspenseQuery,
} from "./canisters/backend"
function MyComponent() {
// Query data
const { data, isPending } = useBackendQuery({
functionName: "get_message",
})
// Mutate data
const { mutate } = useBackendMutation({
functionName: "set_message",
onSuccess: () => console.log("Message updated!"),
})
return (
<div>
<p>{isPending ? "Loading..." : data}</p>
<button onClick={() => mutate(["Hello IC!"])}>Update</button>
</div>
)
}Configuration
Plugin Options
interface IcReactorPluginOptions {
/** List of canisters to generate hooks for */
canisters: CanisterConfig[]
/** Base output directory (default: "./src/canisters") */
outDir?: string
/** Path to import ClientManager from (default: "../../lib/client") */
clientManagerPath?: string
}
interface CanisterConfig {
/** Name of the canister (used for variable naming) */
name: string
/** Path to the .did file */
didFile: string
/** Output directory (default: ./src/canisters/<name>) */
outDir?: string
/** Use DisplayReactor for React-friendly types (default: true) */
useDisplayReactor?: boolean
/** Path to import ClientManager from (relative to generated file) */
clientManagerPath?: string
}Example: Multiple Canisters
// vite.config.ts
import { icReactorPlugin } from "@ic-reactor/vite-plugin"
export default defineConfig({
plugins: [
icReactorPlugin({
clientManagerPath: "@/lib/client", // Global default
canisters: [
{
name: "backend",
didFile: "./backend/backend.did",
},
{
name: "ledger",
didFile: "./ledger/ledger.did",
useDisplayReactor: true, // BigInt → string, etc.
},
{
name: "nft",
didFile: "./nft/nft.did",
outDir: "./src/services/nft", // Custom output
clientManagerPath: "@/lib/nft-client", // Custom client
},
],
}),
],
})Advanced Plugin
For more granular control, use icReactorAdvancedPlugin which generates individual hooks per method:
import { icReactorAdvancedPlugin } from "@ic-reactor/vite-plugin"
export default defineConfig({
plugins: [
icReactorAdvancedPlugin({
canisters: [
{
name: "backend",
didFile: "./backend/backend.did",
},
],
}),
],
})This generates method-specific hooks:
import {
useGetMessageQuery,
useSetMessageMutation,
getMessageQuery, // Static query for no-arg methods
} from "./canisters/backend"
function MyComponent() {
// Method-specific hook
const { data } = useGetMessageQuery([], { staleTime: 5000 })
// Static query usage
const { data: cached } = getMessageQuery.useQuery()
const { mutate } = useSetMessageMutation()
return <div>{data}</div>
}Generated File Structure
src/
├── canisters/
│ └── backend/
│ ├── index.ts # Reactor + hooks
│ └── declarations/
│ └── backend.did.ts # Type declarations
├── lib/
│ └── client.ts # Your ClientManager (not generated)How It Works
- Build Start: The plugin reads your
.didfiles and uses@icp-sdk/bindgento generate TypeScript declarations - Code Generation: Creates a reactor instance and typed hooks for each canister
- Hot Reload: Watches for
.didfile changes and regenerates hooks automatically
DisplayReactor vs Reactor
By default, the plugin uses DisplayReactor which transforms Candid types into React-friendly formats:
| Candid Type | Reactor | DisplayReactor |
| ----------- | ------------ | -------------- |
| nat | bigint | string |
| int | bigint | string |
| principal | Principal | string |
| vec nat8 | Uint8Array | string (hex) |
To use raw Candid types:
icReactorPlugin({
canisters: [
{
name: "backend",
didFile: "./backend/backend.did",
useDisplayReactor: false, // Use Reactor instead
},
],
})Integration with ICP CLI
If you're using icp-cli, configure your vite.config.ts to pass canister IDs via the ic_env cookie:
// vite.config.ts
import { icReactorPlugin } from "@ic-reactor/vite-plugin"
import fs from "fs"
import path from "path"
function loadCanisterIds(): Record<string, string> {
const idsPath = path.resolve(__dirname, ".icp/cache/mappings/local.ids.json")
try {
return JSON.parse(fs.readFileSync(idsPath, "utf-8"))
} catch {
return {}
}
}
const canisterIds = loadCanisterIds()
export default defineConfig({
plugins: [
icReactorPlugin({
canisters: [
{
name: "backend",
didFile: "./backend/backend.did",
},
],
}),
],
server: {
headers: {
"Set-Cookie": `ic_env=${encodeURIComponent(
Object.entries(canisterIds)
.map(([name, id]) => `PUBLIC_CANISTER_ID:${name}=${id}`)
.join("&")
)}; SameSite=Lax;`,
},
},
})Requirements
- Vite 5.x, 6.x, or 7.x
- Node.js 18+
- TypeScript 5.0+
Related Packages
- @ic-reactor/react — React hooks for IC
- @ic-reactor/core — Core reactor functionality
- @icp-sdk/bindgen — Candid binding generator
Documentation
For comprehensive guides and API reference, visit the documentation site.
License
MIT © Behrad Deylami
