DEV Community

Martin Palopoli
Martin Palopoli

Posted on

Un dev loop tipo Vite para un lenguaje compilado: hot reload + preservación de state + manifest en vivo

Parte 13 de la serie Fitz. Se abre el capítulo del frontend: Fitz compila componentes .fitzv a WebAssembly, y este es el dev loop que hace que editarlos se sienta instantáneo — la misma experiencia "guardar y verlo" que te da Vite, sobre un lenguaje que compila a binario nativo.

El setup: un lenguaje compilado con frontend

Fitz es un lenguaje compilado — HTTP, async, Postgres, JWT viven en la sintaxis y emite un binario nativo vía Rust. La historia del frontend es un formato de componentes single-file, .fitzv (state + events + <template>, al estilo Vue/Svelte), que compila a WebAssembly:

fitz build --bin web --target wasm-client   # → target/wasm/web/{web.js, web_bg.wasm}
Enter fullscreen mode Exit fullscreen mode

Sin npm install, sin config de bundler, sin framework externo — el componente se vuelve un bundle WASM autocontenido (el demo del contador pesa 11.4 KB gzipped).

Acá viene la objeción refleja: compilado = feedback lento. Editás, esperás una compilación entera, refrescás el browser a mano. Es lo opuesto a lo que un loop de frontend debería sentirse. Por eso Fitz tiene fitz dev.

El loop

Apuntá fitz dev a un bin wasm-client y deja de ser un compilador para ser un dev server:

fitz dev              # sirve en http://127.0.0.1:1234/
Enter fullscreen mode Exit fullscreen mode

Qué hace:

  • Rebuild incremental con wasm-pack --dev (sin wasm-opt), reusando un crate estable así la cache de cargo queda caliente — el primer build compila las deps, cada save siguiente es de ~1-2 segundos.
  • Un dev server que sirve el root de tu proyecto como python -m http.server: tu index.html, tu CSS, el bundle en target/wasm/<bin>/. ¿Sin index.html? Genera uno mínimo en el punto de mount.
  • Auto-refresh del browser por WebSocket: guardás un .fitzv/.fitz/fitz.toml y la página se recarga sola. Sin F5 a mano.

Guardás, y ~2 segundos después el browser muestra el cambio. En un lenguaje compilado.

El detalle que importa: el state sobrevive el reload

La mayoría de los hot-reload pierden tu estado en un reload completo — ibas tres clicks adentro de un contador, editás el template, y volvés a cero. fitz dev snapshotea el estado vivo del componente antes del reload y lo restaura después:

component App {
  state {
    count: Int = 0
    items: List<Str> = []
  }
  ...
}
Enter fullscreen mode Exit fullscreen mode

Subí count a 7, escribí dos items en la lista, después editá el <h1> de arriba. El reload aterriza y count sigue en 7, la lista sigue con sus dos items. Primitivos, listas, mapas, nullables, tipos nominales custom — todos sobreviven (serializados a JSON en sessionStorage, restaurados al boot). Seguís editando contra el estado exacto con el que estabas probando.

(Caveat honesto: un campo de state preservado gana ante un default cambiado en el template — lo que se preserva es el estado. Cambiá un default y reseteá para verlo.)

El manifest también se re-resuelve en vivo

Esta es la pieza que acaba de salir. Editar un .fitzv siempre se tomaba (el entry se re-lee en cada build) — pero el manifest no. Ahora sí: guardás fitz.toml y fitz dev lo re-resuelve sin reiniciar.

  • Repuntá [bin].main a otro componente → el próximo rebuild usa el nuevo entry.
  • Agregá una [dependencies] — traé un componente de la companion UI (from fitz_liveviews.ui.Badge import Badge) — → resuelto y disponible en el próximo build.
  • Editá [flags] → se toma en vivo.

Y un fitz.toml que rompiste a mitad de edición no mata el loop: imprime el error de parse y sigue sirviendo el bundle anterior, recuperándose en tu próximo save válido.

↻ change in fitz.toml — rebuilding ...
↻ fitz.toml re-resolved
✓ reloaded browser
Enter fullscreen mode Exit fullscreen mode

Fullstack: dos bins, dos terminales

Una app real es un backend más un frontend. En Fitz eso son dos bins en un manifest — un server nativo y un web wasm-client — cableados con @rpc (funciones de servidor llamables desde el componente como si fueran locales). Dev-eás cada mitad en su propia terminal:

# Terminal 1 — el backend
fitz run --bin server

# Terminal 2 — el frontend, hot reload
fitz dev --bin web
Enter fullscreen mode Exit fullscreen mode

--bin <nombre> elige el bin en un proyecto multi-bin; existe igual en fitz run y fitz build.

Por qué es distinto

Los loops de live-reload no son nuevos — la cuestión es qué hace falta para tener uno:

  • Vite / webpack HMR dan un loop excelente, pero son el ecosistema JavaScript: un bundler, un node_modules, un runtime de framework. El loop de Fitz es un CLI sobre un lenguaje que compila a WASM — sin npm en absoluto.
  • cargo watch rebuildea y reinicia, pero no hay reload del browser ni preservación de state — refrescás a mano y arrancás de nuevo.
  • Frameworks WASM de Rust (Dioxus, Leptos) tienen hot-reload, pero estás adoptando un framework + su DSL de macros + su toolchain. Acá el componente es el lenguaje, y fitz dev viene con el compilador.

La combinación — un lenguaje compilado a WASM, rebuilds incrementales, auto-refresh del browser, state que sobrevive el reload, y un manifest que se re-resuelve en vivo — es la parte que ninguna herramienta sola del vecindario te da desde un solo binario.

Los bordes honestos (MVP)

  • Recompila el crate (incremental — "Approach C"). No es todavía un runtime de template data-driven que aplique diffs sin recompilar; sub-segundo-sin-rebuild es un norte futuro más grande. Para el 90% del loop visual, ~2s se siente igual.
  • El modo clásico native/SSR de fitz dev es kill+respawn sin reload del browser (no hay página que refrescar) — el modo wasm-client de arriba es donde vive el loop del browser.
  • Correr ambos bins en UN fitz dev (orquestación dual-process) es un follow-up; hoy son dos terminales.

Ese es el dev loop del frontend cerrado: un lenguaje compilado cuyos componentes editás y ves en el browser en un par de segundos, sin perder tu estado, sin un ecosistema npm abajo. Lo que sigue: cómo el mismo .fitzv se renderiza en el server para el first paint, y el runtime WASM después adopta ese DOM en vez de recrearlo.


Fitz es un lenguaje compilado con tipado gradual, HTTP/async/DB como ciudadanos de primera clase, que compila a binario nativo vía Rust. Su frontend son componentes single-file .fitzv compilados a WebAssembly. Código abierto.

Top comments (0)