ruitl 0.2.2

Template compiler for type-safe, server-rendered HTML components in Rust
Documentation

RUITL - Rust UI Template Language v0.2.2

Crates.io Documentation License: MIT OR Apache-2.0

Status: Pre-release (v0.2.2) — Core feature set stable. Breaking changes possible before v1.0 while SSG and ergonomics settle.

A template compiler for building type-safe HTML components in Rust, modelled on Go's templ. RUITL compiles .ruitl template files into sibling *_ruitl.rs files (committed to source control, reviewable in diffs) and links them into your binary at build time.

Key features

  • Template compilation: .ruitl → sibling *_ruitl.rs (templ-style _templ.go convention)
  • Type safety: Generated components with full Rust type checking
  • Zero runtime parsing: Templates compiled away, pure Rust at render
  • Cargo integration: build.rs and ruitl CLI share one compiler
  • Component props: Type-safe props with validation, defaults, generics
  • Incremental builds: Hash-based skip when .ruitl source is unchanged
  • Watch mode: ruitl compile --watch auto-recompiles on save
  • HTML generation: Clean, deterministic, attribute-order-stable output
  • No JavaScript: Pure Rust, server-side rendering focus

Status

Feature State Notes
Template parser Stable components, props, if/for/match, composition @X(...), imports
Generics Stable (type params) <T, U: Bound>. Lifetime params rejected with explicit error
Codegen Stable Deterministic attribute order; prop bindings emitted only when referenced
Incremental build Stable // ruitl-hash: header skip; CODEGEN_VERSION cache-buster
Watch mode Stable (dev feature) hotwatch-backed; 150ms debounce
Scaffolder Stable ruitl scaffold emits sibling-file projects with bin/ruitl.rs wrapper
Snapshot tests Stable insta + prettyplease; fixtures in tests/fixtures/snapshots/
Minification Optional --features minify post-render via minify-html (planned)
Static site generation Planned ruitl build subcommand with [[routes]] config (planned)
Parser error context Rustc-style frame Line/col + caret + source context
Editor support Stable tree-sitter grammar + LSP w/ diagnostics, formatting, completion (@ + < + prop-names inside @X(...)), hover, go-to-definition
Formatter Stable ruitl fmt [--check] CLI + LSP textDocument/formatting. Idempotent. Preserves leading comments.
Raw-HTML expression Stable {!expr} inside a template body injects the runtime value as raw HTML (no escaping).
Template inheritance Stable @X(...) { body } + {children} slot. Auto-injects pub children: Html on the callee's Props when the slot is used.
Did-you-mean errors Stable Codegen validation suggests closest declared component/prop name on typos via Levenshtein.
Parallel compile Stable compile_dir_sibling fans out with rayon behind the parallel feature (default on).
Buffer-reuse render Stable Html::render_into(&mut String), render_with_capacity, len_hint for hot request loops.
SSR streaming Stable Html::to_chunks() splits a top-level Fragment for hyper::Body::wrap_stream. See examples/streaming_demo.rs.
Dev server Stable (dev + server features) ruitl dev watches .ruitl, serves SSE reload at /ruitl/reload so browsers auto-refresh.
Testing helpers Optional (testing feature) ruitl::testing::{ComponentTestHarness, HtmlAssertion} + assert_html_contains! / assert_renders_to!.
AST debug dump Stable ruitl compile --emit-ast writes a pretty-Debug of the parser AST next to each source.

See tests/fixtures/snapshots/*.snap for canonical codegen output.

Browse the gallery: examples/README.md indexes every .ruitl fixture and example binary by learning goal.

Quick start

You can get started with RUITL in three ways:

Option 1: Explore the RUITL Repository (Development)

Clone and explore the RUITL repository with built-in examples:

# Clone RUITL repository
git clone https://github.com/sirhco/ruitl.git
cd ruitl

# Build RUITL
cargo build

# Compile example templates in the repository
cargo run -- compile

# Run the server integration example with live templates
cargo run --example server_integration
# Server available at http://localhost:3000

This gives you immediate access to working examples and lets you experiment with RUITL templates directly in the repository.

Option 2: Use the Project Scaffolder (Recommended)

Create a complete project with examples and server support:

# Clone RUITL repository
git clone <repository>
cd ruitl

# Create a new project with server and examples
cargo run -- scaffold --name my-project --with-server --with-examples

# Navigate to your new project
cd my-project

# Compile templates and run (using included RUITL binary)
cargo run --bin ruitl -- compile
cargo run
# Server available at http://localhost:3000

This creates a complete project structure with example components, HTTP server, static assets, and documentation.

Option 3: Manual Setup

1. Add RUITL to Your Project

# Cargo.toml
[dependencies]
ruitl = "0.2"
tokio = { version = "1.0", features = ["full"] }

[build-dependencies]
walkdir = "2.3"

2. Create a Template

Create templates/Button.ruitl:

// Button.ruitl - A reusable button component
component Button {
    props {
        text: String,
        variant: String = "primary",
        disabled: bool = false,
    }
}

ruitl Button(text: String, variant: String) {
    <button class={format!("btn btn-{}", variant)} type="button">
        {text}
    </button>
}

3. Use Generated Components

The build process automatically generates Rust components:

use ruitl::prelude::*;

// Generated components are available after build
// mod generated;
// use generated::*;

fn main() -> Result<()> {
    // Component instances
    let button = Button;

    // Type-safe props
    let props = ButtonProps {
        text: "Click Me!".to_string(),
        variant: "primary".to_string(),
    };

    // Render to HTML
    let context = ComponentContext::new();
    let html = button.render(&props, &context)?;

    println!("{}", html.render());
    // Output: <button class="btn btn-primary" type="button">Click Me!</button>

    Ok(())
}

Project scaffolding

ruitl scaffold emits a ready-to-build project skeleton with optional server and example components.

Creating a New Project

# Create a basic RUITL project
cargo run -- scaffold --name my-project

# Create a project with HTTP server support
cargo run -- scaffold --name my-project --with-server

# Create a project with example components
cargo run -- scaffold --name my-project --with-examples

# Create a full-featured project with both server and examples
cargo run -- scaffold --name my-project --with-server --with-examples

# Specify target directory
cargo run -- scaffold --name my-project --target ./projects --with-server

Generated Project Structure

The scaffolder creates a complete project structure:

my-ruitl-project/
├── .gitignore             # Git ignore file
├── Cargo.toml             # Project configuration with dependencies
├── README.md              # Project documentation
├── ruitl.toml             # RUITL-specific configuration
├── bin/
│   └── ruitl.rs           # Included RUITL CLI binary
├── src/
│   ├── main.rs            # Main application (server if --with-server)
│   ├── lib.rs             # Library code
│   └── handlers/          # HTTP handlers (if --with-server)
│       └── mod.rs
├── templates/             # RUITL template files
│   ├── Button.ruitl       # Example button component
│   ├── Card.ruitl         # Example card component
│   └── Layout.ruitl       # Example layout component
├── static/                # Static assets
│   ├── css/
│   │   └── styles.css     # Complete CSS framework
│   └── js/
│       └── main.js        # Interactive JavaScript
├── templates/             # .ruitl sources AND their generated siblings (committed)
│   ├── Button.ruitl
│   ├── Button_ruitl.rs    # Generated sibling (templ-style, checked in)
│   ├── Card.ruitl
│   ├── Card_ruitl.rs
│   ├── Layout.ruitl
│   ├── Layout_ruitl.rs
│   └── mod.rs             # Auto-generated re-exports
└── examples/              # Additional examples (if --with-examples)

Note: Generated *_ruitl.rs files live next to their .ruitl sources (Go templ's _templ.go convention). They are reviewable and checked in. Running cargo run --bin ruitl -- compile or cargo build regenerates them.

Self-Contained Binary: Each scaffolded project includes its own RUITL CLI binary in the bin/ directory, so you don't need to install RUITL globally. All template compilation is done using cargo run --bin ruitl -- <command>.

Server Implementation Features

When using --with-server, the scaffolder generates:

  • Complete HTTP Server: Built with Tokio and Hyper
  • Routing System: Clean URL routing with static file serving
  • Component Integration: Server-side rendering with RUITL components
  • Static Assets: CSS and JavaScript served efficiently
  • Error Handling: 404 pages and error responses
  • Runs immediately with cargo run

Example server routes:

  • http://localhost:3000/ - Home page with welcome content
  • http://localhost:3000/about - About page with project info
  • http://localhost:3000/static/* - Static file serving

Example Components

With --with-examples, you get three complete example components:

Button Component (templates/Button.ruitl):

component Button {
    props {
        text: String,
        variant: String = "primary",
        size: String = "medium",
        disabled: bool = false,
        onclick: String?,
    }
}

ruitl Button(props: ButtonProps) {
    <button
        class={format!("btn btn-{} btn-{}", props.variant, props.size)}
        disabled?={props.disabled}
        onclick={props.onclick.as_deref().unwrap_or("")}
        type="button"
    >
        {props.text}
    </button>
}

Card Component with conditional rendering and Layout Component with full HTML structure are also included.

Getting Started with Scaffolded Project

After scaffolding:

# Navigate to your project
cd my-project

# Compile RUITL templates (using included binary)
cargo run --bin ruitl -- compile

# Build the project
cargo build

# Run the server (if --with-server was used)
cargo run
# Server starts at http://localhost:3000

# Or run as library (if no server)
cargo run

Why the included binary? Each scaffolded project includes its own RUITL CLI wrapper (bin/ruitl.rs) that uses the same RUITL version as your project dependencies. This ensures version consistency and eliminates the need for global RUITL installation.

Scaffold Command Options

Option Description Default
--name <NAME> Project name my-ruitl-project
--target <PATH> Target directory . (current directory)
--with-server Include HTTP server implementation false
--with-examples Include example components false
--verbose Verbose output false

Dependencies Added

The scaffolder automatically configures appropriate dependencies:

Basic Project:

  • ruitl - Template engine
  • serde - Serialization
  • anyhow - Error handling

With Server (adds):

  • tokio - Async runtime
  • hyper - HTTP server
  • serde_json - JSON handling

CLI commands

The ruitl binary drives template compilation, project scaffolding, formatting, and the dev server.

Installation

# Clone and build RUITL
git clone <repository>
cd ruitl
cargo build --release

# Use via cargo run for development
cargo run -- <command>

# Or install globally (after publishing)
cargo install ruitl
ruitl <command>

# In scaffolded projects, use the included binary
cargo run --bin ruitl -- <command>

Available Commands

scaffold - Create New Projects

Create a new RUITL project with optional server and examples:

# Basic usage
ruitl scaffold --name my-project

# Full options
ruitl scaffold \
  --name my-project \
  --target ./projects \
  --with-server \
  --with-examples \
  --verbose

Options:

  • --name <NAME> - Project name (default: my-ruitl-project)
  • --target <PATH> - Target directory (default: current directory)
  • --with-server - Include HTTP server implementation
  • --with-examples - Include example components
  • --verbose - Show detailed output

compile - Compile Templates

Compile .ruitl template files to Rust code. Each Foo.ruitl produces a sibling Foo_ruitl.rs in the same directory (templ-style, checked in).

# Basic compilation (reads templates/, writes sibling *_ruitl.rs files)
ruitl compile

# Specify source directory
ruitl compile --src-dir my-templates

# Watch mode for development
ruitl compile --watch

# Full options
ruitl compile \
  --src-dir ./templates \
  --watch \
  --verbose

Options:

  • --src-dir <PATH> - Template source directory (default: templates)
  • --watch - Watch for file changes and recompile automatically
  • --verbose - Show detailed compilation output

dev - Development Server with Browser Reload

Watch .ruitl files, recompile on save, and push a reload event to any browser subscribed to the sidecar SSE endpoint. Intentionally does NOT manage your app server process — pair it with cargo watch -x run or run your app manually in another terminal.

# Default — watch ./templates, sidecar on port 35729
ruitl dev

# Custom directory and port
ruitl dev --src-dir my-templates --reload-port 40000

Add this script tag to your layout while in development:

<script src="http://127.0.0.1:35729/ruitl/reload.js"></script>

Options:

  • --src-dir <PATH> - Template source directory (default: templates)
  • --reload-port <PORT> - Reload sidecar port (default: 35729)

The server exposes two endpoints:

  • GET /ruitl/reload.js — auto-reconnecting SSE client script.
  • GET /ruitl/reload — SSE stream; fires event: reload after each successful recompile.

version - Show Version

Display RUITL version information:

ruitl version

Global Options

Available for all commands:

  • --config <PATH> - Custom configuration file path
  • --env <ENV> - Environment setting (default: development)
  • --verbose - Enable verbose output
  • --help - Show command help

Configuration File

Create ruitl.toml in your project root:

[project]
name = "my-project"
version = "0.1.0"
description = "My RUITL project"
authors = ["Your Name <your.email@example.com>"]

[build]
template_dir = "templates"
src_dir = "src"

[server]
host = "127.0.0.1"
port = 3000
static_dir = "static"

[dev]
watch = true
hot_reload = false

Development Workflow

Working with Scaffolded Projects

For projects created with the scaffolder:

# 1. Create new project
ruitl scaffold --name my-app --with-server --with-examples

# 2. Navigate to project
cd my-app

# 3. Start development with watch mode (using included binary)
cargo run --bin ruitl -- compile --watch &

# 4. Run the server (in another terminal)
cargo run

# 5. Edit templates and see changes automatically
# Templates in templates/ are watched and recompiled

Working on RUITL Repository

For contributing to RUITL or using repository examples:

# 1. Clone and build RUITL
git clone https://github.com/sirhco/ruitl.git
cd ruitl
cargo build

# 2. Compile repository templates
cargo run -- compile

# 3. Run examples with live reload
cargo run --example server_integration
# Visit http://localhost:3000

# 4. For development with watch mode
cargo run -- compile --watch &

# 5. Edit templates/ files and see changes in examples

Examples

# Create a simple library project
ruitl scaffold --name ui-components --with-examples

# Create a full web application
ruitl scaffold --name my-webapp --with-server --with-examples --target ./projects

# Compile templates with custom source path
ruitl compile --src-dir ./my-templates

# Development workflow with file watching
ruitl compile --watch --verbose

Template syntax

Component Definitions

Define reusable components with type-safe props:

component UserCard {
    props {
        name: String,
        email: String,
        role: String = "user",
        avatar_url: String?,
        is_verified: bool = false,
    }
}

Template Implementation

Implement the component's HTML structure:

ruitl UserCard(name: String, email: String, role: String) {
    <div class="user-card">
        <div class="user-header">
            <h3 class="user-name">{name}</h3>
            <span class="user-role">{role}</span>
        </div>
        <div class="user-contact">
            <p class="user-email">{email}</p>
        </div>
    </div>
}

Expression Interpolation

Use Rust expressions directly in templates:

ruitl Example(count: u32, items: Vec<String>) {
    <div>
        <h1>Items ({count})</h1>
        <p>Status: {if count > 0 { "Has items" } else { "Empty" }}</p>
        <p>First item: {items.first().unwrap_or(&"None".to_string())}</p>
    </div>
}

Template Inheritance via {children}

Pass a body block into a component with @Name(props) { ... } and receive it inside the callee with the {children} slot. Mirrors Go templ's children-prop convention.

component Card {
    props {
        title: String,
    }
}

ruitl Card(title: String) {
    <div class="card">
        <h2>{title}</h2>
        <div class="body">{children}</div>
    </div>
}

ruitl Page() {
    @Card(title: "Welcome".to_string()) {
        <p>First paragraph.</p>
        <p>Second paragraph.</p>
    }
}

Codegen auto-injects pub children: Html into the callee's Props struct whenever its template body references {children}. Call sites without a body block default the field to Html::Empty. Multiple {children} refs in the same template are allowed; each expands to a clone of the slot. The bare identifier {children} is the slot placeholder — {my.children} or any dotted path stays a normal expression.

Build process

build.rs compiles any .ruitl files it finds under src/templates/ and templates/ on cargo build:

Project Structure

my-app/
├── Cargo.toml
├── build.rs                 # Auto-compile templates
├── src/
│   ├── main.rs
│   └── lib.rs
└── templates/              # Your .ruitl files
    ├── Button.ruitl
    ├── UserCard.ruitl
    └── Layout.ruitl

Build Integration

Add to your build.rs:

// build.rs
fn main() {
    // RUITL templates automatically compiled
    println!("cargo:rerun-if-changed=templates");
}

Generated Code

Templates compile to efficient Rust code:

// Generated from Button.ruitl
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ButtonProps {
    pub text: String,
    pub variant: String, // default: "primary"
}

impl ComponentProps for ButtonProps {
    fn validate(&self) -> ruitl::error::Result<()> {
        Ok(())
    }
}

#[derive(Debug)]
pub struct Button;

impl Component for Button {
    type Props = ButtonProps;

    fn render(&self, props: &Self::Props, context: &ComponentContext) -> ruitl::error::Result<Html> {
        Ok(html! {
            <button class={format!("btn btn-{}", props.variant)} type="button">
                {props.text}
            </button>
        })
    }
}

Examples

Scaffolded Project Examples

The best way to explore RUITL is through the project scaffolder:

# Clone RUITL repository
git clone <repository>
cd ruitl

# Create a complete example project
cargo run -- scaffold --name ruitl-demo --with-server --with-examples

# Navigate and run the example
cd ruitl-demo
cargo run --bin ruitl -- compile
cargo run
# Visit http://localhost:3000

This creates a complete project with:

  • Three example components: Button, Card, and Layout
  • HTTP server: Complete web application
  • Static assets: CSS framework and JavaScript
  • Multiple pages: Home, About, and 404 error pages
  • Type-safe props: Demonstrates all RUITL features

Component Examples from Scaffolded Project

Button Component - Shows props with defaults:

// Usage in Rust code
let button = Button;
let props = ButtonProps {
    text: "Click Me!".to_string(),
    variant: "primary".to_string(),
    size: "medium".to_string(),
    disabled: false,
    onclick: Some("handleClick()".to_string()),
};
let html = button.render(&props, &context)?;

Card Component - Shows conditional rendering:

// Usage with optional footer
let card = Card;
let props = CardProps {
    title: "Welcome".to_string(),
    content: "This is a card component with conditional footer.".to_string(),
    footer: Some("Card footer text".to_string()),
    variant: "default".to_string(),
};

Layout Component - Shows full page structure:

// Complete page layout
let layout = Layout;
let props = LayoutProps {
    title: "My App".to_string(),
    description: Some("A RUITL application".to_string()),
    children: "<div>Page content here</div>".to_string(),
};

Repository Examples

The RUITL repository includes several built-in examples you can run immediately:

# Clone and build RUITL
git clone https://github.com/sirhco/ruitl.git
cd ruitl
cargo build

# Run the server integration example (recommended)
cargo run --example server_integration
# Visit http://localhost:3000 to see:
# - Live component rendering
# - Multiple page routing
# - Static asset serving
# - Type-safe component usage

# Other examples available:
cargo run --example basic_usage           # Basic component usage
cargo run --example hello_world          # Simple hello world
cargo run --example html_output_demo     # HTML generation demo
cargo run --example template_compiler_demo # Template compilation demo
cargo run --example advanced_features_demo # Advanced RUITL features

Server Integration Example Features:

  • Live Components: See Button, UserCard, and Page components in action
  • Multiple Routes: Home, Users, About, and API endpoints
  • Server-Side Rendering: Components rendered to HTML
  • Type Safety: Demonstrates prop validation and error handling
  • Navigation: Working page navigation with styled components

Original Demo

You can also run the original template compiler demo:

cargo run --example template_compiler_demo
- Template syntax examples
- Generated code structure
- Build process workflow
- Component usage patterns

## Development workflow

1. **Write Templates**: Create `.ruitl` files in `templates/` directory
2. **Build**: Run `cargo build` to compile templates
3. **Import**: Use generated components in your Rust code
4. **Iterate**: Templates recompile automatically on changes

```bash
# Create new template
echo 'component Hello { props { name: String } }
ruitl Hello(name: String) { <h1>Hello, {name}!</h1> }' > templates/Hello.ruitl

# Compile
cargo build

# Use in your code
# let hello = Hello;
# let props = HelloProps { name: "World".to_string() };
# let html = hello.render(&props, &context)?;

Current status

Working features

  • Build script template compilation
  • CLI template compilation
  • Basic template syntax (components, props, templates)
  • Advanced template syntax (conditionals, loops, composition)
  • Type-safe props with defaults and validation
  • Expression interpolation with complex Rust expressions
  • HTML element generation (all standard elements)
  • Component trait implementation
  • Cargo integration
  • Conditional rendering (if/else statements)
  • Loop rendering (for loops over iterables)
  • Component composition (@Component syntax)
  • Pattern matching (match expressions)
  • Import statements
  • Boolean and primitive type operations
  • Complex nested template structures

Enhancement opportunities

  • Hot reload development mode (ruitl dev — SSE browser reload; pair with cargo watch -x run to restart the app binary)
  • IDE support and syntax highlighting (Zed + VS Code extensions, tree-sitter grammar)
  • Advanced error messages with suggestions (did-you-mean for unknown components/props)
  • Template inheritance ({children} slot + @Card(...) { body } syntax)
  • Performance optimizations (rayon parallel compile, buffer-reuse render API, criterion benches)

Roadmap

  • Advanced template features COMPLETE
  • Hot reload development mode COMPLETE (ruitl dev)
  • IDE support and syntax highlighting COMPLETE
  • Performance optimizations and caching COMPLETE
  • Template inheritance COMPLETE ({children} slot)
  • Server-side streaming COMPLETE (Html::to_chunks + streaming_demo)
  • Component testing utilities COMPLETE (ruitl::testing, testing feature)
  • Template debugging tools PARTIAL (ruitl compile --emit-ast)

Configuration

Configure template compilation in your Cargo.toml:

[package.metadata.ruitl]
template_dir = "templates"
generated_dir = "generated"

Editor support

Four editor-integration crates ship alongside the compiler:

  • tree-sitter-ruitl — tree-sitter grammar for syntax highlighting in Neovim, Helix, Zed, and any tree-sitter-aware editor. Injects the rust language into { ... } expression spans so embedded Rust highlights too.
  • ruitl_lsp — Language Server. Reports parse and codegen errors as textDocument/publishDiagnostics in real time. Supports formatting, completion (@ + < + prop-names inside @X(...)), hover, and go-to-definition. Install via cargo install --path ruitl_lsp.
  • zed-extension-ruitl — Zed extension that bundles the tree-sitter grammar and wires the LSP over stdio. Local install: zed: install dev extension → point at zed-extension-ruitl/.
  • vscode-extension-ruitl — VS Code extension bridging ruitl-lsp plus a TextMate grammar fallback for syntax highlighting. Package locally with npx vsce package && code --install-extension ruitl-0.1.0.vsix.

Roadmap beyond this release:

  • Marketplace publishing — Zed registry + VS Code Marketplace + npm for tree-sitter-ruitl (blocked on publisher account setup, not engineering).
  • Rust-aware expression completion — completion inside {...} depends on a rust-analyzer bridge; intentionally out of scope.

Fallback if you don't wire the LSP: enable watch mode (ruitl compile --watch) in one terminal and let the parser+codegen errors from the watcher guide you.

FAQ

Q: How does RUITL compare to other templating solutions? A: RUITL compiles templates to Rust code at build time, so there is no runtime parser and prop types are checked by rustc.

Q: Can I use existing Rust code in templates? A: Yes. Expressions inside {...} are arbitrary Rust — function calls, method chains, closures.

Q: Is RUITL production ready? A: v0.2 is pre-release. Core and advanced features (conditionals, loops, composition, {children}, generics) are stable; breaking changes are still possible before v1.0 while the static-site story and ergonomics settle.

Q: How does performance compare to runtime templating? A: Templates compile to native Rust with no per-request parser. Render is a tree walk writing escaped text into a Write target.

Contributing

Contributions are welcome. Current priorities:

  • Static-site generation (ruitl build subcommand, planned).
  • Marketplace publishing for the Zed and VS Code extensions.
  • npm release for the tree-sitter-ruitl grammar.
  • Rust-aware completion inside {...} expressions (needs a rust-analyzer bridge).
  • Additional real-world examples beyond server_integration and streaming_demo.

See the status table near the top for what already ships.

License

Licensed under either of

at your option.

Acknowledgments

  • Inspired by templ for Go.
  • Thanks to early contributors and testers.

Contribute via issues.