---
id: tooling/build
title: 'Build: Vite Plugin & CLI'
---

Reads [stift.config.ts](../configuration/config-file.md) and emits the generated runtime + type augmentation consumed by `createStift`.

## Vite plugin

```ts
// vite.config.ts
import { stiftPlugin } from '@secundus-studio/stift-vite-plugin'

export default defineConfig({
  plugins: [stiftPlugin({ devtools: { allowFileWrites: true } })],
})
```

```bash
pnpm add @secundus-studio/stift-vite-plugin@0.4.0
```

First wired in the [Quickstart](../react/quickstart.md).

## Generated files (into `src/` by default)

- `stift.gen.d.ts` — augments `@secundus-studio/stift-core@0.4.0` (`Register`) so message keys, `RegisteredLocale`, and currencies narrow to your real `locales/**`. Flows through `useConfig()`, `useLocale()`, `t()`.
- `virtual:stift/runtime` — `createStiftRuntime()` returns `{ config, catalog, fetchCatalog, source }`; spread it into `createStift(...)`.

Both regenerate whenever the config or `locales/**` change. Each locale×namespace compiles to a binary `.dat` pack (msgpack/cbor + compression per `security`) plus a `catalog.json` of content hashes — the same bytes in dev (served through dev-server middleware) and prod, so dev/prod are byte-identical.

## CLI

The `stift` bin covers non-Vite flows: `generate` (one-shot typegen + pack compile), `watch` (rebuild on locale/config change), `compile` (packs only).

## Devtools write bridge

- `allowFileWrites: true` (Vite plugin) **plus** `allowFileWrites: true` (devtools plugin factory) mount the `/__stift_devtools/write` bridge so the panel's inline editor persists into `locales/**`.
- With the default `false` the route is never mounted — **zero code path can write to disk**.
- Keep it off in prod; the whole devtools surface is gated by `import.meta.env.DEV` anyway.

On a panel write: the file lands in `locales/**` (server logs `[stift] devtools write: <locale>/<ns>#<key> -> <file>`), the plugin **recompiles** the changed namespace(s) and invalidates the virtual modules, and the write is recorded in a suppression set **before** any watcher tick fires — the watcher skips its refresh and suppresses the full page reload, so the edit lands in place with no refresh. Manual IDE edits are *not* suppressed: they hit the watcher and full-reload as usual.

## Next

- The panel itself → [Devtools](./devtools.md)
- Config schema → [stift.config.ts](../configuration/config-file.md)
- Instance + boot → [Setup & Boot Modes](../react/setup.md)
