@ic-reactor/vite-plugin 0.7.2 → 0.9.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,85 @@ 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.
46
+ If you want non-React output, set `target: "core"` and install the matching
47
+ runtime package instead of `@ic-reactor/react`.
77
48
 
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`).
49
+ ## Options
93
50
 
94
51
  ```ts
95
52
  icReactor({
96
53
  canisters: [
97
- { name: "backend", didFile: "./backend/backend.did" },
98
54
  {
99
- name: "workflow_engine",
100
- mode: "Reactor",
101
- didFile: "./workflow/workflow_engine.did",
55
+ name: "backend",
56
+ didFile: "./backend/backend.did",
57
+ mode: "DisplayReactor",
102
58
  },
103
59
  ],
60
+ outDir: "src/declarations",
61
+ clientManagerPath: "../../clients",
62
+ target: "react",
63
+ injectEnvironment: true,
104
64
  })
105
65
  ```
106
66
 
107
- ## Configuration
67
+ ### Per-canister options
68
+
69
+ - `name`
70
+ - `didFile`
71
+ - `outDir`
72
+ - `clientManagerPath`
73
+ - `target`
74
+ - `mode`
75
+ - `canisterId`
76
+
77
+ Supported `mode` values:
78
+
79
+ - `Reactor`
80
+ - `DisplayReactor`
81
+ - `CandidReactor`
82
+ - `CandidDisplayReactor`
83
+ - `MetadataDisplayReactor`
84
+
85
+ Supported `target` values:
86
+
87
+ - `react` (default): generates the reactor plus bound React hooks
88
+ - `core`: generates only the typed reactor exports with no React dependency
108
89
 
109
- ### Plugin Options
90
+ ## Local Development Behavior
110
91
 
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` |
92
+ When `injectEnvironment` is enabled during `vite dev`, the plugin:
117
93
 
118
- ### Canister Config
94
+ 1. asks `icp-cli` for the local network status
95
+ 2. resolves configured canister IDs
96
+ 3. sets the `ic_env` cookie
97
+ 4. proxies `/api` to the local replica
119
98
 
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 |
99
+ If environment detection fails, the plugin still falls back to proxying `/api`
100
+ to `http://127.0.0.1:4943`, but it will not inject canister metadata.
128
101
 
129
- ## Local Development (Environment Injection)
102
+ ## File Regeneration
130
103
 
131
- When running `vite dev`, the plugin automatically handles local canister environment connection:
104
+ On startup and on `.did` file changes, the plugin regenerates declarations and
105
+ the managed `index.generated.ts` implementation. The user-facing `index.ts`
106
+ entry is created once, then preserved unless it still matches the default
107
+ wrapper or a legacy generated scaffold that can be migrated automatically.
108
+ When a watched `.did` file changes, the plugin sends a full browser reload so
109
+ the new declarations are picked up.
132
110
 
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.
111
+ ## When To Use It
136
112
 
137
- This means you **don't** need complex `vite.config.ts` proxy rules or manual `.env` file management for local addresses — it just works.
113
+ - Vite apps with active `.did` iteration
114
+ - teams that want zero extra codegen commands during development
115
+ - projects that want the same output format as the CLI without manual steps
138
116
 
139
- ## License
117
+ ## See Also
140
118
 
141
- MIT © [Behrad Deylami](https://github.com/b3hr4d)
119
+ - Docs: https://ic-reactor.b3pay.net/v3/packages/vite-plugin
120
+ - `@ic-reactor/codegen`: ../codegen/README.md
121
+ - `@ic-reactor/cli`: ../cli/README.md
package/dist/index.cjs CHANGED
@@ -83,16 +83,21 @@ function icReactor(options) {
83
83
  canisters,
84
84
  outDir = "src/declarations",
85
85
  clientManagerPath = "../../clients",
86
+ target = "react",
86
87
  injectEnvironment = true
87
88
  } = options;
88
89
  const globalConfig = {
89
90
  outDir,
90
- clientManagerPath
91
+ clientManagerPath,
92
+ target
91
93
  };
92
94
  const projectRoot = process.cwd();
93
95
  const resolveDidPath = (didFile) => import_node_path.default.normalize(
94
96
  import_node_path.default.isAbsolute(didFile) ? didFile : import_node_path.default.resolve(projectRoot, didFile)
95
97
  );
98
+ const configuredCanisterIds = Object.fromEntries(
99
+ canisters.filter((canister) => !!canister.canisterId).map((canister) => [canister.name, canister.canisterId])
100
+ );
96
101
  const plugin = {
97
102
  name: "ic-reactor-plugin",
98
103
  enforce: "pre",
@@ -118,7 +123,13 @@ function icReactor(options) {
118
123
  }
119
124
  };
120
125
  }
121
- const cookieValue = buildIcEnvCookie(icEnv.canisterIds, icEnv.rootKey);
126
+ const cookieValue = buildIcEnvCookie(
127
+ {
128
+ ...icEnv.canisterIds,
129
+ ...configuredCanisterIds
130
+ },
131
+ icEnv.rootKey
132
+ );
122
133
  return {
123
134
  server: {
124
135
  headers: {
@@ -139,7 +150,7 @@ function icReactor(options) {
139
150
  },
140
151
  async buildStart() {
141
152
  console.log(
142
- `[ic-reactor] Generating hooks for ${canisters.length} canisters...`
153
+ `[ic-reactor] Generating canister bindings for ${canisters.length} canisters...`
143
154
  );
144
155
  for (const canisterConfig of canisters) {
145
156
  try {
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { CanisterConfig } from '@ic-reactor/codegen';
1
+ import { CanisterConfig, CodegenTarget } from '@ic-reactor/codegen';
2
2
 
3
3
  /**
4
4
  * @ic-reactor/vite-plugin
@@ -25,6 +25,11 @@ interface IcReactorPluginOptions {
25
25
  * Default: "../../clients"
26
26
  */
27
27
  clientManagerPath?: string;
28
+ /**
29
+ * Default generated runtime target.
30
+ * Default: "react"
31
+ */
32
+ target?: CodegenTarget;
28
33
  /**
29
34
  * Automatically inject `ic_env` cookie for local development?
30
35
  * Default: true
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CanisterConfig } from '@ic-reactor/codegen';
1
+ import { CanisterConfig, CodegenTarget } from '@ic-reactor/codegen';
2
2
 
3
3
  /**
4
4
  * @ic-reactor/vite-plugin
@@ -25,6 +25,11 @@ interface IcReactorPluginOptions {
25
25
  * Default: "../../clients"
26
26
  */
27
27
  clientManagerPath?: string;
28
+ /**
29
+ * Default generated runtime target.
30
+ * Default: "react"
31
+ */
32
+ target?: CodegenTarget;
28
33
  /**
29
34
  * Automatically inject `ic_env` cookie for local development?
30
35
  * Default: true
package/dist/index.js CHANGED
@@ -51,16 +51,21 @@ function icReactor(options) {
51
51
  canisters,
52
52
  outDir = "src/declarations",
53
53
  clientManagerPath = "../../clients",
54
+ target = "react",
54
55
  injectEnvironment = true
55
56
  } = options;
56
57
  const globalConfig = {
57
58
  outDir,
58
- clientManagerPath
59
+ clientManagerPath,
60
+ target
59
61
  };
60
62
  const projectRoot = process.cwd();
61
63
  const resolveDidPath = (didFile) => path.normalize(
62
64
  path.isAbsolute(didFile) ? didFile : path.resolve(projectRoot, didFile)
63
65
  );
66
+ const configuredCanisterIds = Object.fromEntries(
67
+ canisters.filter((canister) => !!canister.canisterId).map((canister) => [canister.name, canister.canisterId])
68
+ );
64
69
  const plugin = {
65
70
  name: "ic-reactor-plugin",
66
71
  enforce: "pre",
@@ -86,7 +91,13 @@ function icReactor(options) {
86
91
  }
87
92
  };
88
93
  }
89
- const cookieValue = buildIcEnvCookie(icEnv.canisterIds, icEnv.rootKey);
94
+ const cookieValue = buildIcEnvCookie(
95
+ {
96
+ ...icEnv.canisterIds,
97
+ ...configuredCanisterIds
98
+ },
99
+ icEnv.rootKey
100
+ );
90
101
  return {
91
102
  server: {
92
103
  headers: {
@@ -107,7 +118,7 @@ function icReactor(options) {
107
118
  },
108
119
  async buildStart() {
109
120
  console.log(
110
- `[ic-reactor] Generating hooks for ${canisters.length} canisters...`
121
+ `[ic-reactor] Generating canister bindings for ${canisters.length} canisters...`
111
122
  );
112
123
  for (const canisterConfig of canisters) {
113
124
  try {
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.9.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.9.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")
@@ -141,6 +181,7 @@ describe("icReactor", () => {
141
181
  globalConfig: {
142
182
  outDir: "src/declarations",
143
183
  clientManagerPath: "../../clients",
184
+ target: "react",
144
185
  },
145
186
  })
146
187
  })
@@ -187,6 +228,26 @@ describe("icReactor", () => {
187
228
  globalConfig: {
188
229
  outDir: "src/declarations",
189
230
  clientManagerPath: "../../clients",
231
+ target: "react",
232
+ },
233
+ })
234
+ })
235
+
236
+ it("should pass the configured runtime target to codegen", async () => {
237
+ const plugin = createVitePlugin({
238
+ ...mockOptions,
239
+ target: "core",
240
+ })
241
+
242
+ await (plugin.buildStart as any)()
243
+
244
+ expect(runCanisterPipeline).toHaveBeenCalledWith({
245
+ canisterConfig: mockOptions.canisters[0],
246
+ projectRoot: expect.any(String),
247
+ globalConfig: {
248
+ outDir: "src/declarations",
249
+ clientManagerPath: "../../clients",
250
+ target: "core",
190
251
  },
191
252
  })
192
253
  })
package/src/index.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  runCanisterPipeline,
14
14
  type CanisterConfig,
15
15
  type CodegenConfig,
16
+ type CodegenTarget,
16
17
  } from "@ic-reactor/codegen"
17
18
  import { getIcEnvironmentInfo, buildIcEnvCookie } from "./env.js"
18
19
 
@@ -32,6 +33,11 @@ export interface IcReactorPluginOptions {
32
33
  * Default: "../../clients"
33
34
  */
34
35
  clientManagerPath?: string
36
+ /**
37
+ * Default generated runtime target.
38
+ * Default: "react"
39
+ */
40
+ target?: CodegenTarget
35
41
  /**
36
42
  * Automatically inject `ic_env` cookie for local development?
37
43
  * Default: true
@@ -44,19 +50,29 @@ export function icReactor(options: IcReactorPluginOptions): any {
44
50
  canisters,
45
51
  outDir = "src/declarations",
46
52
  clientManagerPath = "../../clients",
53
+ target = "react",
47
54
  injectEnvironment = true,
48
55
  } = options
49
56
 
50
57
  // Construct a partial CodegenConfig to pass to the pipeline
51
- const globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath"> = {
58
+ const globalConfig: Pick<
59
+ CodegenConfig,
60
+ "outDir" | "clientManagerPath" | "target"
61
+ > = {
52
62
  outDir,
53
63
  clientManagerPath,
64
+ target,
54
65
  }
55
66
  const projectRoot = process.cwd()
56
67
  const resolveDidPath = (didFile: string) =>
57
68
  path.normalize(
58
69
  path.isAbsolute(didFile) ? didFile : path.resolve(projectRoot, didFile)
59
70
  )
71
+ const configuredCanisterIds = Object.fromEntries(
72
+ canisters
73
+ .filter((canister) => !!canister.canisterId)
74
+ .map((canister) => [canister.name, canister.canisterId as string])
75
+ )
60
76
 
61
77
  const plugin: Plugin = {
62
78
  name: "ic-reactor-plugin",
@@ -93,7 +109,13 @@ export function icReactor(options: IcReactorPluginOptions): any {
93
109
  }
94
110
  }
95
111
 
96
- const cookieValue = buildIcEnvCookie(icEnv.canisterIds, icEnv.rootKey)
112
+ const cookieValue = buildIcEnvCookie(
113
+ {
114
+ ...icEnv.canisterIds,
115
+ ...configuredCanisterIds,
116
+ },
117
+ icEnv.rootKey
118
+ )
97
119
 
98
120
  return {
99
121
  server: {
@@ -120,7 +142,7 @@ export function icReactor(options: IcReactorPluginOptions): any {
120
142
  // ── Code Generation ──────────────────────────────────────────────────
121
143
 
122
144
  console.log(
123
- `[ic-reactor] Generating hooks for ${canisters.length} canisters...`
145
+ `[ic-reactor] Generating canister bindings for ${canisters.length} canisters...`
124
146
  )
125
147
 
126
148
  for (const canisterConfig of canisters) {