@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 +173 -130
- package/dist/index.cjs +782 -218
- package/dist/index.d.cts +99 -29
- package/dist/index.d.ts +99 -29
- package/dist/index.js +783 -221
- package/package.json +6 -8
- package/src/dev-environment.ts +268 -0
- package/src/env.ts +54 -20
- package/src/generate.ts +594 -0
- package/src/index.ts +665 -396
- package/llms.txt +0 -55
package/README.md
CHANGED
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
# @ic-reactor/vite-plugin
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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:
|
|
43
|
+
canisters: {
|
|
44
|
+
ledger: { didFile: "../backend/ledger.did" },
|
|
45
|
+
},
|
|
27
46
|
}),
|
|
28
47
|
],
|
|
29
48
|
})
|
|
30
49
|
```
|
|
31
50
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
67
|
+
Each entry of `canisters` takes:
|
|
105
68
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
-
|
|
120
|
-
- `MetadataDisplayReactor`
|
|
79
|
+
```ts
|
|
80
|
+
// vite.config.ts
|
|
81
|
+
import { defineConfig } from "vite"
|
|
82
|
+
import { icReactor } from "@ic-reactor/vite-plugin"
|
|
121
83
|
|
|
122
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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
|
|
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
|
|
133
|
-
|
|
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
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
the
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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.
|