Parte 13 de la serie Fitz. Se abre el capítulo del frontend: Fitz compila componentes
.fitzva 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}
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/
Qué hace:
-
Rebuild incremental con
wasm-pack --dev(sinwasm-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: tuindex.html, tu CSS, el bundle entarget/wasm/<bin>/. ¿Sinindex.html? Genera uno mínimo en el punto demount. -
Auto-refresh del browser por WebSocket: guardás un
.fitzv/.fitz/fitz.tomly 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> = []
}
...
}
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].maina 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
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
--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 watchrebuildea 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 devviene 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 deves 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)