1 unstable release
Uses new Rust 2024
| 0.1.0-alpha.1 | Dec 18, 2025 |
|---|
#272 in Template engine
24KB
194 lines
loomx
loomx is a strongly‑typed, server‑driven UI framework for Rust. It renders HTML fragments and pages using typed components, designed for HTMX‑first applications with minimal JavaScript.
At its core, loomx lets you:
- Define components as typed Rust structs
- Render them using Askama templates
- Register components at compile time
- Render fragments dynamically by name
- Integrate cleanly with Axum
- Avoid JavaScript-heavy frontend frameworks
Project Goals
- Strong typing end‑to‑end (props → templates → rendering)
- Server‑driven HTML with HTMX
- Explicit, debuggable architecture
- Framework‑agnostic core
- Thin transport integrations (Axum today, others possible later)
Non‑goals:
- Client‑side virtual DOM
- JS component systems
- Implicit magic routing
Workspace Layout
loomx/
├─ crates/
│ ├─ loomx-core # Registry, rendering, props parsing
│ ├─ loomx-macros # register_component! macro
│ ├─ loomx-axum # Axum integration (routes, handlers)
│ └─ loomx-cli # CLI (experimental)
├─ components/
│ └─ loomx-components-basic
│ └─ Example component pack
├─ examples/
│ └─ basic # Example Axum app
Core Concepts
Components
A component is:
- A Rust struct (typed props)
- An Askama template
- A stable string name
#[derive(Template, Deserialize)]
#[template(path = "components/hello.html")]
pub struct Hello {
pub name: String,
}
impl Component for Hello {
const NAME: &'static str = "hello";
}
register_component!(Hello);
Component Registration
Components are registered at compile time using inventory.
- No global mutable registries
- No runtime plugin loading
- Deterministic startup behavior
Duplicate component names are detected at startup.
Rendering
use loomx::render;
let html = render("hello", json!({ "name": "Ada" }))?;
Rendering returns HTML or a typed error.
Props Parsing (Core)
loomx-core provides a transport‑agnostic parser:
application/jsonapplication/x-www-form-urlencoded
parse_props(content_type, body_bytes)
This logic is reused by all integrations.
Axum Integration
loomx-axum provides:
Generic Fragment Routes
GET /fragments/:name→ query paramsPOST /fragments/:name→ form or JSON body
Router::new().merge(loomx_axum::fragment_router())
Typed Fragment Routes (Ergonomic)
Router::new().merge(
loomx_axum::typed_fragment_routes! {
"/fragments/hello" => ("hello", HelloProps),
}
)
This generates:
GET /fragments/helloPOST /fragments/hello(form)POST /fragments/hello/json(JSON)
All fully typed.
HTMX Support
loomx is HTMX‑first:
- Fragment responses are HTML
- HTMX headers are detected
- Designed for
hx-get,hx-post,hx-swap
No JS framework required.
Logging
Structured logging via tracing:
- Component render attempts
- HTMX detection
- Request metadata
- Startup validation
Safety & Correctness
- Strong typing everywhere
- Duplicate component detection
- Explicit linking of component crates
- No hidden runtime state
Status
loomx is early but functional.
Current focus:
- API stabilization
- Documentation
- CLI & tooling
- More integrations (Actix / Poem)
License
MIT
Update: Typed Rendering & Gallery
loomx now provides an ergonomic typed rendering helper:
render_with<T: Serialize>(name, &T)— preferred for application code
This avoids manual construction of serde_json::Value while preserving the dynamic
component registry model.
The example application includes an interactive component gallery at /gallery
showing:
- inline rendering
- HTMX-powered preview slots
- typed fragment routes under
/typed/...
See docs/gallery.md for details.
Dependencies
~7–11MB
~141K SLoC