Build Integration

Installation

Install the CLI compiler:

curl -fsSL https://waltzing.awesomike.com/install | bash

Build Script

Create a build.rs file to compile templates during build:

Basic Setup (Buffered Mode)

use std::process::Command;

fn main() {
    let out_dir = std::env::var("OUT_DIR").unwrap();

    let status = Command::new("waltzing")
        .args(["-i", "templates", "-o", &out_dir])
        .status()
        .expect("Failed to run waltzing compiler");

    if !status.success() {
        panic!("Waltzing compilation failed");
    }

    println!("cargo:rerun-if-changed=templates");
}

Streaming Mode

let status = Command::new("waltzing")
    .args(["-i", "templates", "-o", &out_dir, "--streaming"])
    .status()
    .expect("Failed to run waltzing compiler");

Async Mode

let status = Command::new("waltzing")
    .args(["-i", "templates", "-o", &out_dir, "--async"])
    .status()
    .expect("Failed to run waltzing compiler");

Output Modes

Waltzing supports three output modes that affect how generated template functions work:

Mode Flag Function Signature Best For
Buffered (default) fn apply(...) -> Content Simple usage, returns complete HTML
Streaming --streaming fn apply(buf: &mut String, ...) Memory efficiency, incremental output
Async --async async fn apply(w: &mut W, ...) -> io::Result<()> Async runtimes, streaming responses

Using Generated Templates

Buffered Mode (Default)

Templates return a Content type that can be converted to a String:

mod templates {
    include!(concat!(env!("OUT_DIR"), "/mod.rs"));
}

fn render_page(title: &str, posts: Vec) -> String {
    templates::pages::home::apply(title, posts).to_string()
}

Streaming Mode

Templates write directly to a buffer. Use the buffered! macro to collect output:

mod templates {
    include!(concat!(env!("OUT_DIR"), "/mod.rs"));
}

fn render_page(title: &str, posts: Vec) -> String {
    // Access buffered! macro
    templates::rt::buffered!(templates::pages::home::apply(title, posts))
}

Async Mode

Templates are async and write to an AsyncWrite. The buffered! macro returns a future:

mod templates {
    include!(concat!(env!("OUT_DIR"), "/mod.rs"));
}

async fn render_page(title: &str, posts: Vec) -> std::io::Result {
    templates::rt::buffered!(templates::pages::home::apply(title, posts)).await
}

The buffered! Macro

In streaming and async modes, the buffered! macro collects template output into a String instead of writing to a provided buffer.

Syntax

// Without arguments
templates::rt::buffered!(templates::component::apply())

// With arguments
templates::rt::buffered!(templates::pages::home::apply(title, posts))
Why rt?

The macro is namespaced under rt (runtime) to avoid conflicts when using multiple template directories compiled with different output modes.

CLI Options

Option Description
-i, --input Input template directory (can specify multiple, supports alias=path syntax)
-o, --output Output directory for generated Rust code
--streaming Generate streaming functions
--async Generate async streaming functions
--with-axum Enable axum::IntoResponse impl
--with-uuid Enable From<Uuid> impl
--preserve-whitespace Keep original whitespace (default: minify)
--debug-comments Emit HTML comments at component boundaries

With Axum

When compiled with --with-axum, Content implements IntoResponse:

use axum::{routing::get, Router};

mod templates {
    include!(concat!(env!("OUT_DIR"), "/mod.rs"));
}

async fn home() -> impl axum::response::IntoResponse {
    templates::pages::home::apply("Welcome")
}

fn main() {
    let app = Router::new().route("/", get(home));
    // ...
}