@ic-reactor/vite-plugin 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,40 +1,19 @@
1
1
  # @ic-reactor/vite-plugin
2
2
 
3
- <div align="center">
4
- <strong>Zero-config Vite plugin for auto-generating IC reactor hooks from Candid files.</strong>
5
- <br><br>
6
-
7
- [![npm version](https://img.shields.io/npm/v/@ic-reactor/vite-plugin.svg)](https://www.npmjs.com/package/@ic-reactor/vite-plugin)
8
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
9
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
10
- </div>
3
+ Vite plugin for IC Reactor code generation. It runs the shared
4
+ `@ic-reactor/codegen` pipeline, watches `.did` files, and can inject the
5
+ `ic_env` cookie used by `ClientManager` during local development.
11
6
 
12
- ---
13
-
14
- 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 using the shared `@ic-reactor/codegen` pipeline.
15
-
16
- ## Features
17
-
18
- - ⚡ **Zero Config** — Point to your `.did` file and get hooks instantly
19
- - 🔄 **Hot Reload** — Automatically regenerates hooks and types when `.did` files change
20
- - 📦 **TypeScript Declarations** — Full built-in type safety
21
- - 🌍 **Auto Environment** — Automatically detects local replica and injects `ic_env` cookie
22
-
23
- ## Installation
7
+ ## Install
24
8
 
25
9
  ```bash
26
- # With pnpm
27
10
  pnpm add -D @ic-reactor/vite-plugin
28
-
29
- # Required peer dependencies
30
11
  pnpm add @ic-reactor/react @tanstack/react-query @icp-sdk/core
31
12
  ```
32
13
 
33
14
  ## Quick Start
34
15
 
35
- ### 1. Configure the Plugin
36
-
37
- ```typescript
16
+ ```ts
38
17
  // vite.config.ts
39
18
  import { defineConfig } from "vite"
40
19
  import react from "@vitejs/plugin-react"
@@ -44,22 +23,13 @@ export default defineConfig({
44
23
  plugins: [
45
24
  react(),
46
25
  icReactor({
47
- canisters: [
48
- {
49
- name: "backend",
50
- didFile: "./backend/backend.did",
51
- },
52
- ],
26
+ canisters: [{ name: "backend", didFile: "./backend/backend.did" }],
53
27
  }),
54
28
  ],
55
29
  })
56
30
  ```
57
31
 
58
- ### 2. Create Your ClientManager
59
-
60
- The plugin looks for a client manager to import. By default, it expects it at `../../clients` relative to the generated files.
61
-
62
- ```typescript
32
+ ```ts
63
33
  // src/clients.ts
64
34
  import { ClientManager } from "@ic-reactor/react"
65
35
  import { QueryClient } from "@tanstack/react-query"
@@ -67,75 +37,75 @@ import { QueryClient } from "@tanstack/react-query"
67
37
  export const queryClient = new QueryClient()
68
38
  export const clientManager = new ClientManager({
69
39
  queryClient,
70
- withCanisterEnv: true, // Important for cookie injection support
40
+ withCanisterEnv: true,
71
41
  })
72
42
  ```
73
43
 
74
- ### 3. Use Generated Hooks
44
+ The plugin generates files under `src/declarations/<canister>/` by default.
75
45
 
76
- The plugin generates headers in `src/declarations/<name>/index.ts` by default.
77
-
78
- ```tsx
79
- import { useBackendQuery } from "./declarations/backend"
80
-
81
- function MyComponent() {
82
- const { data, isPending } = useBackendQuery({
83
- functionName: "get_message",
84
- })
85
-
86
- return <p>{isPending ? "Loading..." : data}</p>
87
- }
88
- ```
89
-
90
- ### Reactor Mode (per canister)
91
-
92
- By default, generated hooks use `DisplayReactor` (backward compatible). Set `mode` on a canister to choose the generated reactor class (`Reactor`, `DisplayReactor`, `CandidReactor`, `CandidDisplayReactor`, or `MetadataDisplayReactor`).
46
+ ## Options
93
47
 
94
48
  ```ts
95
49
  icReactor({
96
50
  canisters: [
97
- { name: "backend", didFile: "./backend/backend.did" },
98
51
  {
99
- name: "workflow_engine",
100
- mode: "Reactor",
101
- didFile: "./workflow/workflow_engine.did",
52
+ name: "backend",
53
+ didFile: "./backend/backend.did",
54
+ mode: "DisplayReactor",
102
55
  },
103
56
  ],
57
+ outDir: "src/declarations",
58
+ clientManagerPath: "../../clients",
59
+ injectEnvironment: true,
104
60
  })
105
61
  ```
106
62
 
107
- ## Configuration
63
+ ### Per-canister options
64
+
65
+ - `name`
66
+ - `didFile`
67
+ - `outDir`
68
+ - `clientManagerPath`
69
+ - `mode`
70
+ - `canisterId`
71
+
72
+ Supported `mode` values:
73
+
74
+ - `Reactor`
75
+ - `DisplayReactor`
76
+ - `CandidReactor`
77
+ - `CandidDisplayReactor`
78
+ - `MetadataDisplayReactor`
108
79
 
109
- ### Plugin Options
80
+ ## Local Development Behavior
110
81
 
111
- | Option | Type | Description | Default |
112
- | :------------------ | :----------------- | :-------------------------------------------------- | :------------------- |
113
- | `canisters` | `CanisterConfig[]` | List of canisters to generate hooks for (required). | - |
114
- | `outDir` | `string` | Base output directory for generated files. | `"src/declarations"` |
115
- | `clientManagerPath` | `string` | Path to client manager import. | `"../../clients"` |
116
- | `injectEnvironment` | `boolean` | Inject `ic_env` cookie for local development. | `true` |
82
+ When `injectEnvironment` is enabled during `vite dev`, the plugin:
117
83
 
118
- ### Canister Config
84
+ 1. asks `icp-cli` for the local network status
85
+ 2. resolves configured canister IDs
86
+ 3. sets the `ic_env` cookie
87
+ 4. proxies `/api` to the local replica
119
88
 
120
- | Option | Type | Description | Required |
121
- | :------------------ | :------------------------------------------------------------------------------------------------------- | :---------------------------------------------- | :------- |
122
- | `name` | `string` | Name of the canister (used for variable names). | Yes |
123
- | `didFile` | `string` | Path to the `.did` file. | Yes |
124
- | `outDir` | `string` | Override output directory for this canister. | No |
125
- | `clientManagerPath` | `string` | Override client manager path. | No |
126
- | `mode` | `"Reactor" \| "DisplayReactor" \| "CandidReactor" \| "CandidDisplayReactor" \| "MetadataDisplayReactor"` | Reactor class for generated hooks. | No |
127
- | `canisterId` | `string` | Optional fixed canister ID. | No |
89
+ If environment detection fails, the plugin still falls back to proxying `/api`
90
+ to `http://127.0.0.1:4943`, but it will not inject canister metadata.
128
91
 
129
- ## Local Development (Environment Injection)
92
+ ## File Regeneration
130
93
 
131
- When running `vite dev`, the plugin automatically handles local canister environment connection:
94
+ On startup and on `.did` file changes, the plugin regenerates declarations and
95
+ the managed `index.generated.ts` implementation. The user-facing `index.ts`
96
+ entry is created once, then preserved unless it still matches the default
97
+ wrapper or a legacy generated scaffold that can be migrated automatically.
98
+ When a watched `.did` file changes, the plugin sends a full browser reload so
99
+ the new declarations are picked up.
132
100
 
133
- 1. Detects your local environment (using `icp` or `dfx` CLI).
134
- 2. Sets an `ic_env` cookie containing the root key and canister IDs.
135
- 3. Sets up a proxy for `/api` to your local replica.
101
+ ## When To Use It
136
102
 
137
- This means you **don't** need complex `vite.config.ts` proxy rules or manual `.env` file management for local addresses — it just works.
103
+ - Vite apps with active `.did` iteration
104
+ - teams that want zero extra codegen commands during development
105
+ - projects that want the same output format as the CLI without manual steps
138
106
 
139
- ## License
107
+ ## See Also
140
108
 
141
- MIT © [Behrad Deylami](https://github.com/b3hr4d)
109
+ - Docs: https://ic-reactor.b3pay.net/v3/packages/vite-plugin
110
+ - `@ic-reactor/codegen`: ../codegen/README.md
111
+ - `@ic-reactor/cli`: ../cli/README.md
package/dist/index.cjs CHANGED
@@ -93,6 +93,9 @@ function icReactor(options) {
93
93
  const resolveDidPath = (didFile) => import_node_path.default.normalize(
94
94
  import_node_path.default.isAbsolute(didFile) ? didFile : import_node_path.default.resolve(projectRoot, didFile)
95
95
  );
96
+ const configuredCanisterIds = Object.fromEntries(
97
+ canisters.filter((canister) => !!canister.canisterId).map((canister) => [canister.name, canister.canisterId])
98
+ );
96
99
  const plugin = {
97
100
  name: "ic-reactor-plugin",
98
101
  enforce: "pre",
@@ -118,7 +121,13 @@ function icReactor(options) {
118
121
  }
119
122
  };
120
123
  }
121
- const cookieValue = buildIcEnvCookie(icEnv.canisterIds, icEnv.rootKey);
124
+ const cookieValue = buildIcEnvCookie(
125
+ {
126
+ ...icEnv.canisterIds,
127
+ ...configuredCanisterIds
128
+ },
129
+ icEnv.rootKey
130
+ );
122
131
  return {
123
132
  server: {
124
133
  headers: {
package/dist/index.js CHANGED
@@ -61,6 +61,9 @@ function icReactor(options) {
61
61
  const resolveDidPath = (didFile) => path.normalize(
62
62
  path.isAbsolute(didFile) ? didFile : path.resolve(projectRoot, didFile)
63
63
  );
64
+ const configuredCanisterIds = Object.fromEntries(
65
+ canisters.filter((canister) => !!canister.canisterId).map((canister) => [canister.name, canister.canisterId])
66
+ );
64
67
  const plugin = {
65
68
  name: "ic-reactor-plugin",
66
69
  enforce: "pre",
@@ -86,7 +89,13 @@ function icReactor(options) {
86
89
  }
87
90
  };
88
91
  }
89
- const cookieValue = buildIcEnvCookie(icEnv.canisterIds, icEnv.rootKey);
92
+ const cookieValue = buildIcEnvCookie(
93
+ {
94
+ ...icEnv.canisterIds,
95
+ ...configuredCanisterIds
96
+ },
97
+ icEnv.rootKey
98
+ );
90
99
  return {
91
100
  server: {
92
101
  headers: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ic-reactor/vite-plugin",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "Vite plugin for zero-config IC reactor generation from Candid files",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -37,14 +37,14 @@
37
37
  "url": "https://github.com/B3Pay/ic-reactor/issues"
38
38
  },
39
39
  "dependencies": {
40
- "@ic-reactor/codegen": "0.7.2"
40
+ "@ic-reactor/codegen": "0.8.0"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@icp-sdk/bindgen": "^0.2.2",
47
- "@types/node": "^25.3.0",
47
+ "@types/node": "^25.3.5",
48
48
  "tsup": "^8.5.1",
49
49
  "typescript": "^5.9.3",
50
50
  "vitest": "^4.0.18"
package/src/index.test.ts CHANGED
@@ -110,6 +110,46 @@ describe("icReactor", () => {
110
110
  expect(config.server.proxy["/api"].target).toBe("http://127.0.0.1:4943")
111
111
  })
112
112
 
113
+ it("should prefer configured canisterId over CLI-discovered env values", () => {
114
+ ;(execFileSync as any).mockImplementation(
115
+ (command: string, args: string[], options: any) => {
116
+ if (
117
+ command === "icp" &&
118
+ args.includes("network") &&
119
+ args.includes("status")
120
+ ) {
121
+ return JSON.stringify({ root_key: "mock-root-key", port: 4943 })
122
+ }
123
+ if (
124
+ command === "icp" &&
125
+ args.includes("canister") &&
126
+ args.includes("status")
127
+ ) {
128
+ return "local-cli-canister-id"
129
+ }
130
+ return ""
131
+ }
132
+ )
133
+
134
+ const plugin = createVitePlugin({
135
+ ...mockOptions,
136
+ canisters: [
137
+ {
138
+ ...mockOptions.canisters[0],
139
+ canisterId: "yq4ns-hyaaa-aaaap-akbna-cai",
140
+ },
141
+ ],
142
+ })
143
+ const config = (plugin as any).config({}, { command: "serve" })
144
+
145
+ expect(config.server.headers["Set-Cookie"]).toContain(
146
+ "PUBLIC_CANISTER_ID%3Atest_canister%3Dyq4ns-hyaaa-aaaap-akbna-cai"
147
+ )
148
+ expect(config.server.headers["Set-Cookie"]).not.toContain(
149
+ "PUBLIC_CANISTER_ID%3Atest_canister%3Dlocal-cli-canister-id"
150
+ )
151
+ })
152
+
113
153
  it("should fallback to default proxy when icp-cli fails", () => {
114
154
  ;(execFileSync as any).mockImplementation(() => {
115
155
  throw new Error("Command not found")
package/src/index.ts CHANGED
@@ -57,6 +57,11 @@ export function icReactor(options: IcReactorPluginOptions): any {
57
57
  path.normalize(
58
58
  path.isAbsolute(didFile) ? didFile : path.resolve(projectRoot, didFile)
59
59
  )
60
+ const configuredCanisterIds = Object.fromEntries(
61
+ canisters
62
+ .filter((canister) => !!canister.canisterId)
63
+ .map((canister) => [canister.name, canister.canisterId as string])
64
+ )
60
65
 
61
66
  const plugin: Plugin = {
62
67
  name: "ic-reactor-plugin",
@@ -93,7 +98,13 @@ export function icReactor(options: IcReactorPluginOptions): any {
93
98
  }
94
99
  }
95
100
 
96
- const cookieValue = buildIcEnvCookie(icEnv.canisterIds, icEnv.rootKey)
101
+ const cookieValue = buildIcEnvCookie(
102
+ {
103
+ ...icEnv.canisterIds,
104
+ ...configuredCanisterIds,
105
+ },
106
+ icEnv.rootKey
107
+ )
97
108
 
98
109
  return {
99
110
  server: {