Codegen workflows
Three mechanisms regenerate the TypeScript client from your @specs. They do the same work. Pick by situation.
Compiler hook: the steady-state default
Add :elixir_ts_rpc to your compilers: list. The client regenerates on every mix compile, so it stays in sync with your @specs with zero extra processes.
This is the recommended default for ordinary development.
Auto-reload with Phoenix
In a Phoenix app the compiler hook gives you a hands-off loop: edit a handler or its @spec and the regenerated client is ready for the browser, no extra watcher to run. Phoenix's dev code reloader recompiles your changed Elixir, the appended :elixir_ts_rpc compiler regenerates the client, and the Vite dev server hot-reloads it. The Phoenix example is wired exactly this way.
Three touchpoints set it up.
1. Append the compiler in mix.exs, after the standard compilers, so it regenerates from freshly built BEAM:
def project do
[
# ...
compilers: [:phoenix_live_view] ++ Mix.compilers() ++ [:elixir_ts_rpc],
listeners: [Phoenix.CodeReloader]
]
end2. Point the compiler at your router and output file in config/config.exs:
config :elixir_ts_rpc,
router: MyAppWeb.RpcRouter,
out: Path.expand("../../client/src/rpc.gen.ts", __DIR__)Put :out inside your Vite client's src/ so the dev server watches it. Add the optional {:file_system, "~> 1.0", only: :dev} dependency too.
3. Keep Phoenix's dev code reloader on, the default in config/dev.exs:
config :my_app, MyAppWeb.Endpoint, code_reloader: trueThe reload chain
- You edit a handler or its
@spec. - Phoenix's code reloader recompiles the changed modules.
- The
:elixir_ts_rpccompiler regeneratesrpc.gen.ts, but only when the generated output actually changed, so unrelated edits don't churn the file. - Vite sees the file change and hot-reloads the client with the new types.
Regenerates on the next request
Phoenix's code reloader recompiles on the next request to the app, so the client regenerates the next time the browser hits Phoenix. If you want regeneration the instant you save, independent of any request, run mix rpc.gen.ts.watch alongside the server instead. That is the Watch task below, which uses the same :file_system dep.
Watch task: while iterating against the TS dev server
mix rpc.gen.ts.watchRecompiles and regenerates on each Elixir source change so the Vite/HMR side picks up new types immediately. Best when you're moving fast between Elixir and the browser. Requires the optional :file_system dependency.
Explicit task: for CI and one-off generation
mix rpc.gen.ts --router MyApp.Router --out path/to/rpc.gen.tsUse this in CI to verify the committed rpc.gen.ts is up to date. Generate it, then fail the build if git diff is non-empty. It also works for a one-off regeneration.
TIP
The basic example's CI does exactly this. It regenerates the client from the router and typechecks it, so a drifted spec breaks the build. See the integration job.