loomx

Crates.ioloomx
lib.rsloomx
version0.1.0-alpha.1
created_at2025-12-18 23:17:21.026663+00
updated_at2025-12-18 23:17:21.026663+00
descriptionTyped headless rendering for HTMX
homepage
repositoryhttps://github.com/cvandeluitgaarden/loomx-rs
max_upload_size
id1993804
size24,491
Christian van de Luitgaarden (cvandeluitgaarden)

documentation

README

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/json
  • application/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 params
  • POST /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/hello
  • POST /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.

Commit count: 0

cargo fmt