@ic-reactor/vite-plugin 0.4.0 → 0.5.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
@@ -11,15 +11,14 @@
11
11
 
12
12
  ---
13
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.
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
15
 
16
16
  ## Features
17
17
 
18
18
  - ⚡ **Zero Config** — Point to your `.did` file and get hooks instantly
19
- - 🔄 **Hot Reload** — Automatically regenerates hooks when `.did` files change
20
- - 📦 **TypeScript Declarations** — Full type safety with auto-generated types
21
- - 🎯 **Display Types** — Optional `DisplayReactor` support for React-friendly types
22
- - 🔌 **Flexible** — Works with any `ClientManager` configuration
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
23
22
 
24
23
  ## Installation
25
24
 
@@ -58,26 +57,29 @@ export default defineConfig({
58
57
 
59
58
  ### 2. Create Your ClientManager
60
59
 
61
- The plugin expects you to have a `ClientManager` exported from a file. By default, it looks for `./src/lib/clients.ts`:
60
+ The plugin looks for a client manager to import. By default, it expects it at `../../clients` relative to the generated files.
62
61
 
63
62
  ```typescript
64
- // src/lib/clients.ts
63
+ // src/clients.ts
65
64
  import { ClientManager } from "@ic-reactor/react"
66
65
  import { QueryClient } from "@tanstack/react-query"
67
66
 
68
67
  export const queryClient = new QueryClient()
69
- export const clientManager = new ClientManager({ queryClient })
68
+ export const clientManager = new ClientManager({
69
+ queryClient,
70
+ withCanisterEnv: true, // Important for cookie injection support
71
+ })
70
72
  ```
71
73
 
72
74
  ### 3. Use Generated Hooks
73
75
 
74
- The plugin generates an `index.ts` file in your canister folder (default: `./src/lib/canisters/<name>/index.ts`):
76
+ The plugin generates headers in `src/declarations/<name>/index.ts` by default.
75
77
 
76
78
  ```tsx
77
- import { useActorQuery, useActorMutation } from "./canisters/backend"
79
+ import { useBackendQuery } from "./declarations/backend"
78
80
 
79
81
  function MyComponent() {
80
- const { data, isPending } = useActorQuery({
82
+ const { data, isPending } = useBackendQuery({
81
83
  functionName: "get_message",
82
84
  })
83
85
 
@@ -89,116 +91,32 @@ function MyComponent() {
89
91
 
90
92
  ### Plugin Options
91
93
 
92
- | Option | Type | Description | Default |
93
- | :------------------ | :----------------- | :--------------------------------------------------- | :---------------------- |
94
- | `canisters` | `CanisterConfig[]` | List of canisters to generate hooks for. | `[]` |
95
- | `outDir` | `string` | Base output directory for generated files. | `"./src/lib/canisters"` |
96
- | `injectEnvironment` | `boolean` | Whether to inject canister IDs into `ic_env` cookie. | `true` |
97
- | `clientManagerPath` | `string` | Path to a custom `ClientManager` instance. | `"../../clients"` |
94
+ | Option | Type | Description | Default |
95
+ | :------------------ | :----------------- | :-------------------------------------------------- | :------------------- |
96
+ | `canisters` | `CanisterConfig[]` | List of canisters to generate hooks for (required). | - |
97
+ | `outDir` | `string` | Base output directory for generated files. | `"src/declarations"` |
98
+ | `clientManagerPath` | `string` | Path to client manager import. | `"../../clients"` |
99
+ | `injectEnvironment` | `boolean` | Inject `ic_env` cookie for local development. | `true` |
98
100
 
99
101
  ### Canister Config
100
102
 
101
- | Option | Type | Description | Required |
102
- | :------------------ | :-------- | :-------------------------------------------------- | :------- |
103
- | `name` | `string` | Name of the canister (used in generated code). | Yes |
104
- | `didFile` | `string` | Path to the `.did` file. | Yes |
105
- | `outDir` | `string` | Override output directory for this canister. | No |
106
- | `useDisplayReactor` | `boolean` | Use `DisplayReactor` instead of standard `Reactor`. | `true` |
107
- | `clientManagerPath` | `string` | Override client manager path for this canister. | No |
108
-
109
- ## How It Works
110
-
111
- 1. **Build Start**: The plugin reads your `.did` files and generates TypeScript declarations (`.js` and `.d.ts`).
112
- 2. **Code Generation**: Creates a reactor instance and typed hooks (using `createActorHooks`) for each canister in `index.ts`.
113
- 3. **Hot Reload**: Watches for `.did` file changes and regenerates everything automatically.
114
- 4. **Local Proxy**: Configures a Vite proxy to redirect `/api` calls to your local replica.
115
- 5. **Environment Detection**: Automatically injects canister IDs from `icp-cli` or `dfx` cache into your session.
116
-
117
- ## DisplayReactor vs Reactor
118
-
119
- By default, the plugin uses `DisplayReactor` which transforms Candid types into React-friendly formats:
120
-
121
- | Candid Type | Reactor | DisplayReactor |
122
- | ----------- | ------------ | -------------- |
123
- | `nat` | `bigint` | `string` |
124
- | `int` | `bigint` | `string` |
125
- | `principal` | `Principal` | `string` |
126
- | `vec nat8` | `Uint8Array` | `string` (hex) |
127
-
128
- To use raw Candid types:
129
-
130
- ```typescript
131
- icReactorPlugin({
132
- canisters: [
133
- {
134
- name: "backend",
135
- didFile: "./backend/backend.did",
136
- useDisplayReactor: false, // Use Reactor instead
137
- },
138
- ],
139
- })
140
- ```
141
-
142
- ## Integration with ICP CLI
143
-
144
- `@ic-reactor/vite-plugin` now supports **zero-config local `icp-cli` canister env injection** during `vite dev`.
145
-
146
- When dev server starts, the plugin automatically tries to read:
147
-
148
- - `.icp/cache/mappings/local.ids.json`
149
-
150
- If present, it sets an `ic_env` cookie with:
151
-
152
- - `ic_root_key=<local-root-key>`
153
- - `PUBLIC_CANISTER_ID:<name>=<canister-id>`
154
-
155
- This means `withCanisterEnv: true` works out of the box after `icp deploy`, without custom cookie code in `vite.config.ts`.
156
-
157
- ```typescript
158
- // vite.config.ts
159
- import { defineConfig } from "vite"
160
- import react from "@vitejs/plugin-react"
161
- import { icReactorPlugin } from "@ic-reactor/vite-plugin"
162
-
163
- export default defineConfig({
164
- plugins: [
165
- react(),
166
- icReactorPlugin({
167
- canisters: [
168
- {
169
- name: "backend",
170
- didFile: "./backend/backend.did",
171
- },
172
- ],
173
- }),
174
- ],
175
- })
176
- ```
177
-
178
- If you need to disable this behavior:
179
-
180
- ```typescript
181
- icReactorPlugin({
182
- canisters: [...],
183
- injectEnvironment: false,
184
- })
185
- ```
186
-
187
- ## Requirements
188
-
189
- - **Vite 5.x, 6.x, or 7.x**
190
- - **Node.js 18+**
191
- - **TypeScript 5.0+**
103
+ | Option | Type | Description | Required |
104
+ | :------------------ | :------- | :---------------------------------------------- | :------- |
105
+ | `name` | `string` | Name of the canister (used for variable names). | Yes |
106
+ | `didFile` | `string` | Path to the `.did` file. | Yes |
107
+ | `outDir` | `string` | Override output directory for this canister. | No |
108
+ | `clientManagerPath` | `string` | Override client manager path. | No |
109
+ | `canisterId` | `string` | Optional fixed canister ID. | No |
192
110
 
193
- ## Related Packages
111
+ ## Local Development (Environment Injection)
194
112
 
195
- - [@ic-reactor/react](https://www.npmjs.com/package/@ic-reactor/react) React hooks for IC
196
- - [@ic-reactor/core](https://www.npmjs.com/package/@ic-reactor/core) — Core reactor functionality
197
- - [@icp-sdk/bindgen](https://www.npmjs.com/package/@icp-sdk/bindgen) — Candid binding generator
113
+ When running `vite dev`, the plugin automatically handles local canister environment connection:
198
114
 
199
- ## Documentation
115
+ 1. Detects your local environment (using `icp` or `dfx` CLI).
116
+ 2. Sets an `ic_env` cookie containing the root key and canister IDs.
117
+ 3. Sets up a proxy for `/api` to your local replica.
200
118
 
201
- For comprehensive guides and API reference, visit the [documentation site](https://b3pay.github.io/ic-reactor/v3).
119
+ This means you **don't** need complex `vite.config.ts` proxy rules or manual `.env` file management for local addresses — it just works.
202
120
 
203
121
  ## License
204
122
 
package/dist/index.cjs CHANGED
@@ -33,16 +33,19 @@ __export(index_exports, {
33
33
  icReactorPlugin: () => icReactorPlugin
34
34
  });
35
35
  module.exports = __toCommonJS(index_exports);
36
- var import_fs = __toESM(require("fs"), 1);
37
- var import_path = __toESM(require("path"), 1);
38
- var import_child_process = require("child_process");
36
+ var import_node_path = __toESM(require("path"), 1);
39
37
  var import_codegen = require("@ic-reactor/codegen");
38
+
39
+ // src/env.ts
40
+ var import_child_process = require("child_process");
40
41
  function getIcEnvironmentInfo(canisterNames) {
41
42
  const environment = process.env.ICP_ENVIRONMENT || "local";
42
43
  try {
43
44
  const networkStatus = JSON.parse(
44
45
  (0, import_child_process.execFileSync)("icp", ["network", "status", "-e", environment, "--json"], {
45
- encoding: "utf-8"
46
+ encoding: "utf-8",
47
+ stdio: ["ignore", "pipe", "ignore"]
48
+ // suppress stderr
46
49
  })
47
50
  );
48
51
  const rootKey = networkStatus.root_key;
@@ -53,35 +56,51 @@ function getIcEnvironmentInfo(canisterNames) {
53
56
  const canisterId = (0, import_child_process.execFileSync)(
54
57
  "icp",
55
58
  ["canister", "status", name, "-e", environment, "-i"],
56
- {
57
- encoding: "utf-8"
58
- }
59
+ { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }
59
60
  ).trim();
60
- canisterIds[name] = canisterId;
61
+ if (canisterId) {
62
+ canisterIds[name] = canisterId;
63
+ }
61
64
  } catch {
62
65
  }
63
66
  }
64
67
  return { environment, rootKey, proxyTarget, canisterIds };
65
- } catch {
68
+ } catch (error) {
66
69
  return null;
67
70
  }
68
71
  }
69
72
  function buildIcEnvCookie(canisterIds, rootKey) {
70
- const envParts = [`ic_root_key=${rootKey}`];
73
+ const parts = [`ic_root_key=${rootKey}`];
71
74
  for (const [name, id] of Object.entries(canisterIds)) {
72
- envParts.push(`PUBLIC_CANISTER_ID:${name}=${id}`);
75
+ parts.push(`PUBLIC_CANISTER_ID:${name}=${id}`);
73
76
  }
74
- return encodeURIComponent(envParts.join("&"));
77
+ return encodeURIComponent(parts.join("&"));
75
78
  }
79
+
80
+ // src/index.ts
76
81
  function icReactorPlugin(options) {
77
- const baseOutDir = options.outDir ?? "./src/lib/canisters";
82
+ const {
83
+ canisters,
84
+ outDir = "src/declarations",
85
+ clientManagerPath = "../../clients",
86
+ injectEnvironment = true
87
+ } = options;
88
+ const globalConfig = {
89
+ outDir,
90
+ clientManagerPath
91
+ };
78
92
  return {
79
93
  name: "ic-reactor-plugin",
80
- config(_config, { command }) {
81
- if (command !== "serve" || !(options.injectEnvironment ?? true)) {
94
+ enforce: "pre",
95
+ // Run before other plugins
96
+ config(config, { command }) {
97
+ if (command !== "serve" || !injectEnvironment) {
82
98
  return {};
83
99
  }
84
- const canisterNames = options.canisters.map((c) => c.name);
100
+ const canisterNames = canisters.map((c) => c.name).filter((n) => !!n);
101
+ if (!canisterNames.includes("internet_identity")) {
102
+ canisterNames.push("internet_identity");
103
+ }
85
104
  const icEnv = getIcEnvironmentInfo(canisterNames);
86
105
  if (!icEnv) {
87
106
  return {
@@ -111,91 +130,53 @@ function icReactorPlugin(options) {
111
130
  };
112
131
  },
113
132
  async buildStart() {
114
- const defaultClientPath = import_path.default.resolve(
115
- process.cwd(),
116
- "src/lib/clients.ts"
133
+ const projectRoot = process.cwd();
134
+ console.log(
135
+ `[ic-reactor] Generating hooks for ${canisters.length} canisters...`
117
136
  );
118
- if (!import_fs.default.existsSync(defaultClientPath)) {
119
- console.log(
120
- `[ic-reactor] Default client manager not found. Creating at ${defaultClientPath}`
121
- );
122
- const clientContent = (0, import_codegen.generateClientFile)();
123
- import_fs.default.mkdirSync(import_path.default.dirname(defaultClientPath), { recursive: true });
124
- import_fs.default.writeFileSync(defaultClientPath, clientContent);
125
- }
126
- for (const canister of options.canisters) {
127
- let didFile = canister.didFile;
128
- const outDir = canister.outDir ?? import_path.default.join(baseOutDir, canister.name);
129
- if (!didFile) {
130
- const environment = process.env.ICP_ENVIRONMENT || "local";
131
- console.log(
132
- `[ic-reactor] didFile not specified for "${canister.name}". Attempting to download from canister...`
133
- );
134
- try {
135
- const candidContent = (0, import_child_process.execFileSync)(
136
- "icp",
137
- [
138
- "canister",
139
- "metadata",
140
- canister.name,
141
- "candid:service",
142
- "-e",
143
- environment
144
- ],
145
- { encoding: "utf-8" }
146
- ).trim();
147
- const declarationsDir = import_path.default.join(outDir, "declarations");
148
- if (!import_fs.default.existsSync(declarationsDir)) {
149
- import_fs.default.mkdirSync(declarationsDir, { recursive: true });
150
- }
151
- didFile = import_path.default.join(declarationsDir, `${canister.name}.did`);
152
- import_fs.default.writeFileSync(didFile, candidContent);
153
- console.log(
154
- `[ic-reactor] Candid downloaded and saved to ${didFile}`
155
- );
156
- } catch (error) {
137
+ for (const canisterConfig of canisters) {
138
+ try {
139
+ const result = await (0, import_codegen.runCanisterPipeline)({
140
+ canisterConfig,
141
+ projectRoot,
142
+ globalConfig
143
+ });
144
+ if (!result.success) {
157
145
  console.error(
158
- `[ic-reactor] Failed to download candid for ${canister.name}: ${error}`
146
+ `[ic-reactor] Failed to generate ${canisterConfig.name}: ${result.error}`
159
147
  );
160
- continue;
148
+ } else {
161
149
  }
162
- }
163
- console.log(
164
- `[ic-reactor] Generating hooks for ${canister.name} from ${didFile}`
165
- );
166
- const result = await (0, import_codegen.generateDeclarations)({
167
- didFile,
168
- outDir,
169
- canisterName: canister.name
170
- });
171
- if (!result.success) {
150
+ } catch (err) {
172
151
  console.error(
173
- `[ic-reactor] Failed to generate declarations: ${result.error}`
152
+ `[ic-reactor] Error generating ${canisterConfig.name}:`,
153
+ err
174
154
  );
175
- continue;
176
155
  }
177
- const reactorContent = (0, import_codegen.generateReactorFile)({
178
- canisterName: canister.name,
179
- didFile,
180
- clientManagerPath: canister.clientManagerPath ?? options.clientManagerPath
181
- });
182
- const reactorPath = import_path.default.join(outDir, "index.ts");
183
- import_fs.default.mkdirSync(outDir, { recursive: true });
184
- import_fs.default.writeFileSync(reactorPath, reactorContent);
185
- console.log(`[ic-reactor] Reactor hooks generated at ${reactorPath}`);
186
156
  }
187
157
  },
188
158
  handleHotUpdate({ file, server }) {
189
159
  if (file.endsWith(".did")) {
190
- const canister = options.canisters.find((c) => {
191
- if (!c.didFile) return false;
192
- return import_path.default.resolve(c.didFile) === file;
160
+ const affectedCanister = canisters.find((c) => {
161
+ const configPath = import_node_path.default.resolve(process.cwd(), c.didFile);
162
+ return configPath === file;
193
163
  });
194
- if (canister) {
164
+ if (affectedCanister) {
195
165
  console.log(
196
- `[ic-reactor] Detected change in ${file}, regenerating...`
166
+ `[ic-reactor] .did file changed: ${affectedCanister.name}. Regenerating...`
197
167
  );
198
- server.restart();
168
+ const projectRoot = process.cwd();
169
+ (0, import_codegen.runCanisterPipeline)({
170
+ canisterConfig: affectedCanister,
171
+ projectRoot,
172
+ globalConfig
173
+ }).then((result) => {
174
+ if (result.success) {
175
+ server.ws.send({ type: "full-reload" });
176
+ } else {
177
+ console.error(`[ic-reactor] Regeneration failed: ${result.error}`);
178
+ }
179
+ });
199
180
  }
200
181
  }
201
182
  }
package/dist/index.d.cts CHANGED
@@ -1,55 +1,37 @@
1
1
  import { Plugin } from 'vite';
2
+ import { CanisterConfig } from '@ic-reactor/codegen';
2
3
 
3
4
  /**
4
- * IC-Reactor Vite Plugin
5
+ * @ic-reactor/vite-plugin
5
6
  *
6
- * A Vite plugin that generates ic-reactor hooks from Candid .did files.
7
- * Uses @ic-reactor/codegen for all code generation logic.
8
- *
9
- * Usage:
10
- * ```ts
11
- * import { icReactorPlugin } from "@ic-reactor/vite-plugin"
12
- *
13
- * export default defineConfig({
14
- * plugins: [
15
- * icReactorPlugin({
16
- * canisters: [
17
- * {
18
- * name: "backend",
19
- * didFile: "../backend/backend.did",
20
- * clientManagerPath: "../lib/client"
21
- * }
22
- * ]
23
- * })
24
- * ]
25
- * })
26
- * ```
7
+ * Vite plugin that:
8
+ * 1. Generates hooks at build time (using @ic-reactor/codegen pipeline)
9
+ * 2. Injects `ic_env` cookie for local development (via proxy)
10
+ * 3. Hot-reloads when .did files change
27
11
  */
28
12
 
29
- interface CanisterConfig {
30
- name: string;
31
- outDir?: string;
32
- didFile?: string;
33
- clientManagerPath?: string;
34
- }
35
13
  interface IcReactorPluginOptions {
36
- /** List of canisters to generate hooks for */
14
+ /**
15
+ * Canister configurations.
16
+ * `name` is required for each canister.
17
+ */
37
18
  canisters: CanisterConfig[];
38
- /** Base output directory (default: ./src/lib/canisters) */
19
+ /**
20
+ * Default output directory (relative to project root).
21
+ * Default: "src/declarations"
22
+ */
39
23
  outDir?: string;
40
24
  /**
41
- * Path to import ClientManager from (relative to generated file).
25
+ * Default client manager import path.
42
26
  * Default: "../../clients"
43
27
  */
44
28
  clientManagerPath?: string;
45
29
  /**
46
- * Automatically inject the IC environment (canister IDs and root key)
47
- * into the browser using an `ic_env` cookie. (default: true)
48
- *
49
- * This is useful for local development with `icp`.
30
+ * Automatically inject `ic_env` cookie for local development?
31
+ * Default: true
50
32
  */
51
33
  injectEnvironment?: boolean;
52
34
  }
53
35
  declare function icReactorPlugin(options: IcReactorPluginOptions): Plugin;
54
36
 
55
- export { type CanisterConfig, type IcReactorPluginOptions, icReactorPlugin };
37
+ export { type IcReactorPluginOptions, icReactorPlugin };
package/dist/index.d.ts CHANGED
@@ -1,55 +1,37 @@
1
1
  import { Plugin } from 'vite';
2
+ import { CanisterConfig } from '@ic-reactor/codegen';
2
3
 
3
4
  /**
4
- * IC-Reactor Vite Plugin
5
+ * @ic-reactor/vite-plugin
5
6
  *
6
- * A Vite plugin that generates ic-reactor hooks from Candid .did files.
7
- * Uses @ic-reactor/codegen for all code generation logic.
8
- *
9
- * Usage:
10
- * ```ts
11
- * import { icReactorPlugin } from "@ic-reactor/vite-plugin"
12
- *
13
- * export default defineConfig({
14
- * plugins: [
15
- * icReactorPlugin({
16
- * canisters: [
17
- * {
18
- * name: "backend",
19
- * didFile: "../backend/backend.did",
20
- * clientManagerPath: "../lib/client"
21
- * }
22
- * ]
23
- * })
24
- * ]
25
- * })
26
- * ```
7
+ * Vite plugin that:
8
+ * 1. Generates hooks at build time (using @ic-reactor/codegen pipeline)
9
+ * 2. Injects `ic_env` cookie for local development (via proxy)
10
+ * 3. Hot-reloads when .did files change
27
11
  */
28
12
 
29
- interface CanisterConfig {
30
- name: string;
31
- outDir?: string;
32
- didFile?: string;
33
- clientManagerPath?: string;
34
- }
35
13
  interface IcReactorPluginOptions {
36
- /** List of canisters to generate hooks for */
14
+ /**
15
+ * Canister configurations.
16
+ * `name` is required for each canister.
17
+ */
37
18
  canisters: CanisterConfig[];
38
- /** Base output directory (default: ./src/lib/canisters) */
19
+ /**
20
+ * Default output directory (relative to project root).
21
+ * Default: "src/declarations"
22
+ */
39
23
  outDir?: string;
40
24
  /**
41
- * Path to import ClientManager from (relative to generated file).
25
+ * Default client manager import path.
42
26
  * Default: "../../clients"
43
27
  */
44
28
  clientManagerPath?: string;
45
29
  /**
46
- * Automatically inject the IC environment (canister IDs and root key)
47
- * into the browser using an `ic_env` cookie. (default: true)
48
- *
49
- * This is useful for local development with `icp`.
30
+ * Automatically inject `ic_env` cookie for local development?
31
+ * Default: true
50
32
  */
51
33
  injectEnvironment?: boolean;
52
34
  }
53
35
  declare function icReactorPlugin(options: IcReactorPluginOptions): Plugin;
54
36
 
55
- export { type CanisterConfig, type IcReactorPluginOptions, icReactorPlugin };
37
+ export { type IcReactorPluginOptions, icReactorPlugin };