@ic-reactor/vite-plugin 0.14.0 → 4.0.0-beta.1

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,14 +1,33 @@
1
1
  # @ic-reactor/vite-plugin
2
2
 
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.
3
+ > **ic-reactor 4 is a prerelease.** `4.0.0-beta.1` is published under npm's
4
+ > `beta` dist-tag, and `latest` stays the 0.15 plugin of the 3.x line until 4.0
5
+ > GA. That plugin, which generates reactor bindings with `@ic-reactor/codegen`,
6
+ > is documented at https://ic-reactor.b3pay.net/v3/packages/vite-plugin.
7
+
8
+ A Vite plugin for an app built on a module that `candid-core-cli gen` generates
9
+ from a `.did` file. It does two things, and exports only `icReactor` and the
10
+ type `IcReactorPluginOptions`:
11
+
12
+ - **Generation.** It runs `candid-core-cli gen` on each configured `.did` file
13
+ when a build or the dev server starts, and again when that file changes. The
14
+ generated module is candid-core's, as the generator wrote it: the plugin
15
+ adds no wrapper files, hooks or reactors.
16
+ - **Environment.** Under `vite dev` and `vite preview` it sets the `ic_env`
17
+ cookie and proxies `/api` to the local IC network, so the app finds its
18
+ canister IDs and the replica's root key without configuration.
6
19
 
7
20
  ## Install
8
21
 
9
- ```bash
10
- pnpm add -D @ic-reactor/vite-plugin
11
- pnpm add @ic-reactor/react @tanstack/react-query @icp-sdk/core
22
+ The plugin runs the `@candid-core/cli` your app installs, and that CLI has to
23
+ pair with the `@candid-core/schema` runtime the generated modules import. Both
24
+ are pinned to one exact release while they are betas, and so is the plugin's
25
+ peer on the CLI:
26
+
27
+ ```sh
28
+ npm install --save-exact @candid-core/schema@0.3.0-beta.1
29
+ npm install --save-dev --save-exact @candid-core/cli@0.2.0-beta.1
30
+ npm install --save-dev @ic-reactor/vite-plugin@beta
12
31
  ```
13
32
 
14
33
  ## Quick Start
@@ -16,122 +35,150 @@ pnpm add @ic-reactor/react @tanstack/react-query @icp-sdk/core
16
35
  ```ts
17
36
  // vite.config.ts
18
37
  import { defineConfig } from "vite"
19
- import react from "@vitejs/plugin-react"
20
38
  import { icReactor } from "@ic-reactor/vite-plugin"
21
39
 
22
40
  export default defineConfig({
23
41
  plugins: [
24
- react(),
25
42
  icReactor({
26
- canisters: [{ name: "backend", didFile: "./backend/backend.did" }],
43
+ canisters: {
44
+ ledger: { didFile: "../backend/ledger.did" },
45
+ },
27
46
  }),
28
47
  ],
29
48
  })
30
49
  ```
31
50
 
32
- ```ts
33
- // src/clients.ts
34
- import { ClientManager } from "@ic-reactor/react"
35
- import { QueryClient } from "@tanstack/react-query"
36
-
37
- export const queryClient = new QueryClient()
38
- export const clientManager = new ClientManager({
39
- queryClient,
40
- })
41
- ```
51
+ On `vite dev` and `vite build` this writes `src/canisters/ledger.ts`, the
52
+ generated module (it exports `actor` and the type `Actor`), and
53
+ `src/canisters/ledger.envelope.json` next to it. The generator names its output
54
+ after the `.did` file, not after the key in `canisters`.
42
55
 
43
- No opt-in flag is needed to pick up the plugin's environment in development:
44
- the plugin sets the `ic_env` cookie and `ClientManager` reads it automatically
45
- in the browser. That trust stops at the local replica — cookies are not
46
- origin-isolated, so on a custom domain or mainnet the cookie is ignored and a
47
- reactor with no `canisterId` throws. Set the per-canister `canisterId` in the
48
- plugin config to bake it into the generated output for those builds, or pass
49
- `allowEnvConfig: true` to `ClientManager` if you trust every subdomain of the
50
- domain you serve from.
51
-
52
- The plugin generates files under `src/declarations/<canister>/` by default —
53
- `declarations/<did-basename>.{js,d.ts,did}` plus a managed `index.generated.ts`
54
- and a stable `index.ts` wrapper. With `target: "react"`, `index.generated.ts`
55
- exports the reactor and six hooks named after the canister
56
- (`use<Canister>Query`, `use<Canister>SuspenseQuery`,
57
- `use<Canister>InfiniteQuery`, `use<Canister>SuspenseInfiniteQuery`,
58
- `use<Canister>Mutation`, `use<Canister>Method`).
59
-
60
- If Prettier resolves from Vite's `config.root`, the plugin formats the
61
- generated `.js` and `.d.ts` with it and your Prettier config, so a rebuild
62
- leaves formatted, committed declarations unchanged. Without Prettier they hold
63
- the Candid parser's output followed by a newline, and a formatting error never
64
- fails the build.
65
-
66
- If you want non-React output, set `target: "core"` and install the matching
67
- runtime package instead of `@ic-reactor/react`.
56
+ The first line the plugin logs says where an agent reads how to use the
57
+ library: `ic-reactor: agent guide at node_modules/@ic-reactor/core/llms.txt`.
68
58
 
69
59
  ## Options
70
60
 
71
- ```ts
72
- icReactor({
73
- canisters: [
74
- {
75
- name: "backend",
76
- didFile: "./backend/backend.did",
77
- mode: "DisplayReactor",
78
- },
79
- ],
80
- outDir: "src/declarations",
81
- clientManagerPath: "../../clients",
82
- target: "react",
83
- injectEnvironment: true,
84
- failOnError: true,
85
- })
86
- ```
87
-
88
- Relative paths — `didFile`, `outDir` — resolve against Vite's resolved
89
- `config.root`, not the directory vite was started from. If you set
90
- `root: "frontend"`, write the paths as the project itself sees them.
91
-
92
- Note `--config` alone does **not** change the root: `vite build --config
93
- apps/web/vite.config.ts` still leaves `root` at the directory vite was started
94
- from, so app-relative paths resolve against the monorepo root. Set `root` in the
95
- config file, or pass it positionally (`vite build apps/web --config …`), for
96
- those paths to mean what the app expects.
97
-
98
- `failOnError` decides what a failed canister does to the run. It defaults to
99
- `true` under `vite build` and `false` under `vite dev`: a build that quietly
100
- ships the bindings left over from the last successful run is worse than no
101
- build at all, while a dev server has to survive the broken intermediate states
102
- of a `.did` file being edited.
61
+ | Option | Default | Meaning |
62
+ | ------------------- | ------------------------- | --------------------------------------------------------------------------------- |
63
+ | `canisters` | `{}` | The app's canisters, by their name in the `icp` project; see below |
64
+ | `injectEnvironment` | `true` | Set the cookie and the `/api` proxy under `vite dev` and `vite preview` |
65
+ | `failOnError` | build `true`, dev `false` | Abort the Vite run when a canister fails to generate; see "When generation fails" |
103
66
 
104
- ### Per-canister options
67
+ Each entry of `canisters` takes:
105
68
 
106
- - `name`
107
- - `didFile`
108
- - `outDir`
109
- - `clientManagerPath`
110
- - `target`
111
- - `mode`
112
- - `canisterId`
69
+ | Field | Default | Meaning |
70
+ | ------------ | ----------------- | -------------------------------------------------------------------------------------- |
71
+ | `didFile` | none | The canister's Candid file, relative to the Vite root. Without it nothing is generated |
72
+ | `outDir` | `"src/canisters"` | Where the generator writes, relative to the Vite root |
73
+ | `canisterId` | none | A fixed ID for the cookie, which wins over the one `icp` reports |
113
74
 
114
- Supported `mode` values:
75
+ Canisters that share one interface share one module. An ICP ledger and a ckBTC
76
+ ledger both on `icrc1.did` are two entries, so that the `ic_env` cookie carries
77
+ each ID, and one generated module:
115
78
 
116
- - `Reactor`
117
- - `DisplayReactor`
118
- - `CandidReactor`
119
- - `CandidDisplayReactor`
120
- - `MetadataDisplayReactor`
79
+ ```ts
80
+ // vite.config.ts
81
+ import { defineConfig } from "vite"
82
+ import { icReactor } from "@ic-reactor/vite-plugin"
121
83
 
122
- Supported `target` values:
84
+ export default defineConfig({
85
+ plugins: [
86
+ icReactor({
87
+ canisters: {
88
+ icp_ledger: { didFile: "did/icrc1.did" },
89
+ ckbtc_ledger: { didFile: "did/icrc1.did" },
90
+ },
91
+ }),
92
+ ],
93
+ })
94
+ ```
123
95
 
124
- - `react` (default): generates the reactor plus bound React hooks
125
- - `core`: generates only the typed reactor exports with no React dependency
96
+ The generator is given `icrc1.did` once, writes `src/canisters/icrc1.ts` once,
97
+ and a save of the file regenerates it once. (Naming a canister without a
98
+ `didFile` is the same thing when only the cookie is wanted.) Different `.did`
99
+ files that name the same module cannot share an `outDir`: `a/ledger.did` and
100
+ `b/ledger.did` both mean `ledger.ts`, and the second is refused with a message
101
+ naming both. Give one an `outDir` of its own. Names that differ only in case
102
+ (`Ledger.did` and `ledger.did`) collide only where the filesystem of the
103
+ `outDir` ignores case, as macOS and Windows do by default: the plugin checks
104
+ the filesystem and does not refuse them where it tells them apart.
105
+
106
+ ## Generation
107
+
108
+ The generator is WebAssembly, so the plugin does not load it: it runs the bin
109
+ script of the `@candid-core/cli` installed for your app (resolved from the Vite
110
+ root, so a monorepo's hoisted copy is found) with the running Node binary, in a
111
+ child process and not through a shell. A trap, a crash or a runaway loop on a
112
+ bad `.did` ends that process and the plugin reports it. The dev server is not
113
+ affected.
114
+
115
+ - Canisters that write into one `outDir` share one process, and each `outDir`
116
+ has its own. A process that dies without a report is run again, one
117
+ canister at a time and side by side, so the failure lands on the canister
118
+ that caused it.
119
+ - A process that runs longer than 60 seconds is killed, and counts as one that
120
+ died: when it was generating several canisters, each is run again alone and
121
+ only the one that hangs fails. A hang therefore costs up to two timeouts
122
+ (120 seconds) before the others are generated.
123
+ - What the generator writes to stderr is logged as a warning, a line at a time
124
+ and as it arrives, naming the `.did` files its process is generating. With
125
+ `--json` the generator reports in its JSON document, so what shows up here
126
+ is a crash's output or a warning from the runtime. When a process fails, its
127
+ stderr is also in the error message, so that text appears twice. Past 200
128
+ lines from one process the rest is not logged, so a runaway generator does
129
+ not flood the terminal.
130
+ - Closing the dev server (or restarting it, as a `vite.config` edit does) kills
131
+ a generator that is still running and drops the runs still waiting, so none
132
+ outlives the server it belonged to or runs beside the next one.
133
+ - A declaration the generator cannot represent is left out of the module, and
134
+ the plugin logs each one as a warning, for example
135
+ `ic-reactor: ledger: omitted declaration Bad (reserved_field_name)`.
136
+ - Editing a `.did` regenerates only the canisters that name it. Saves that
137
+ arrive while it runs collapse into one more run. `vite build --watch`
138
+ regenerates a canister only when its `.did` text changed.
139
+ - Deleting a `.did` under `vite dev` fails the canisters that name it at once,
140
+ in the log and the overlay: the module it generated is still on disk, and
141
+ would otherwise look current. Putting the file back regenerates them.
142
+
143
+ ### When generation fails
144
+
145
+ A failed canister costs that canister, and nothing else:
146
+
147
+ - Under `vite build` the build fails, with the generator's diagnostics (or its
148
+ stderr, if it crashed) in the error message. A build that exits 0 would ship
149
+ the bindings left over from the last good run.
150
+ - Under `vite dev` the failure is logged and shown in the browser's error
151
+ overlay, and the server keeps serving. The overlay lists the canisters that
152
+ are still broken: fixing one of several updates it, and fixing the last
153
+ clears it. Vite's client clears an error overlay whenever it applies a hot
154
+ update, as it does for Vite's own errors, so a canister that is still broken
155
+ can drop out of the browser's overlay after you save some other file. Its
156
+ error stays in the terminal log, and a full reload shows the overlay again.
157
+ - `failOnError` overrides either default.
158
+
159
+ ### In CI
160
+
161
+ `candid-core-cli gen --check` compares the generated files with the ones on
162
+ disk, writes nothing, and exits 1 on any difference. Run it with the same
163
+ arguments the plugin uses to fail a pipeline when committed output is stale:
164
+
165
+ ```json
166
+ {
167
+ "scripts": {
168
+ "gen:check": "candid-core-cli gen ../backend/ledger.did -o src/canisters --check"
169
+ }
170
+ }
171
+ ```
126
172
 
127
173
  ## Local Development Behavior
128
174
 
129
- When `injectEnvironment` is enabled during `vite dev`, the plugin:
175
+ When `injectEnvironment` is enabled during `vite dev` or `vite preview`, the
176
+ plugin:
130
177
 
131
178
  1. asks `icp` for the local network status
132
- 2. resolves canister IDs — `internet_identity` is added automatically if not
133
- already in your canister list
134
- 3. sets the `ic_env` cookie
179
+ 2. resolves canister IDs: the keys of `canisters`, and `internet_identity`,
180
+ which is added automatically if not already listed
181
+ 3. sets the `ic_env` cookie on each response
135
182
  4. proxies `/api` to the local replica
136
183
 
137
184
  If a canister has a `canisterId` set in the plugin config, that value overrides
@@ -140,36 +187,32 @@ the auto-detected ID for that canister.
140
187
  Set the `ICP_ENVIRONMENT` environment variable to target a non-default network
141
188
  (defaults to `"local"`).
142
189
 
143
- If environment detection fails, the plugin still falls back to proxying `/api`
144
- to `http://127.0.0.1:4943`, but it will not inject canister metadata. It warns
145
- when that happens with canisters configured, because the failure is otherwise
146
- indistinguishable from success until the app breaks on an undefined canister
147
- id. Run with `DEBUG=ic-reactor` to see the `icp` output behind the warning.
148
-
149
- If your Vite config already sets `server.proxy["/api"]`, the plugin leaves that
150
- entry alone, whether detection succeeds or not.
151
-
152
- ## File Regeneration
153
-
154
- On startup and on `.did` file changes, the plugin regenerates declarations and
155
- the managed `index.generated.ts` implementation. The user-facing `index.ts`
156
- entry is created once, then preserved unless it still matches the default
157
- wrapper or a legacy generated scaffold that can be migrated automatically.
158
- When a watched `.did` file changes, the plugin sends a full browser reload so
159
- the new declarations are picked up. Regeneration is serialized per canister —
160
- saves that land while a run is in flight collapse into a single rerun — so two
161
- rapid saves cannot interleave inside the pipeline's delete-then-write sequence.
162
- A regeneration that fails is reported to the terminal and to the browser error
163
- overlay rather than leaving the page on stale bindings.
164
-
165
- ## When To Use It
166
-
167
- - Vite apps with active `.did` iteration
168
- - teams that want zero extra codegen commands during development
169
- - projects that want the same output format as the CLI without manual steps
170
-
171
- ## See Also
172
-
173
- - Docs: https://ic-reactor.b3pay.net/v3/packages/vite-plugin
174
- - `@ic-reactor/codegen`: ../codegen/README.md
175
- - `@ic-reactor/cli`: ../cli/README.md
190
+ If environment detection fails, the plugin falls back to proxying `/api` to
191
+ `http://127.0.0.1:4943`, and sets no cookie, or with no canisters configured one
192
+ that names only icp-cli's built-in Internet Identity. It warns when that happens
193
+ with canisters configured, and when a configured canister has no ID, because
194
+ the failure is otherwise indistinguishable from success until the app breaks on
195
+ an undefined canister id. Run with `DEBUG=ic-reactor` to see the `icp` output
196
+ behind the warning.
197
+
198
+ Detection is complete once `icp` reports the network and every configured
199
+ canister has an ID. Until then the plugin asks `icp` again on each page load,
200
+ and that page gets the answer: start `vite dev` first, then run
201
+ `icp network start` and `icp deploy`, and reload the page. The `/api` proxy
202
+ moves to the network `icp` reports, the fallback included. Once detection is
203
+ complete, page loads run no further `icp` commands, so redeploying into a
204
+ fresh network, with new canister IDs and a new root key, needs a dev server
205
+ restart. A configured canister you never deploy locally keeps detection
206
+ incomplete, so every page load runs `icp` for it; set its `canisterId` and it
207
+ counts as resolved. If you never run a local network, set
208
+ `injectEnvironment: false` and page loads run no `icp`.
209
+
210
+ If your Vite config or another plugin sets `server.proxy["/api"]`, the plugin
211
+ leaves that entry alone, whether detection succeeds or not, and that proxy does
212
+ not follow detection.
213
+
214
+ ## Tests
215
+
216
+ Vitest runs the plugin in mode `test`. There the plugin injects no environment
217
+ and never runs `icp`, whatever `injectEnvironment` says. Generation is not an
218
+ environment concern and still runs, so the modules a test imports exist.