Skip to main content

Embedding Luat as a Library

Luat can be used as a Rust library for server-side template rendering without the CLI. This is ideal for embedding Luat into existing Rust applications or custom server frameworks.

Installation

Add Luat to your Cargo.toml:

[dependencies]
luat = "0.1"

Basic Usage

use luat::{Engine, FileSystemResolver};
use serde_json::json;

fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a resolver that loads templates from the filesystem
let resolver = FileSystemResolver::new("./templates");

// Create an engine with memory caching (100 templates max)
let engine = Engine::with_memory_cache(resolver, 100)?;

// Compile a template
let module = engine.compile_entry("hello.luat")?;

// Create context data
let context = engine.to_value(json!({
"name": "World",
"items": ["Apple", "Banana", "Cherry"]
}))?;

// Render the template
let html = engine.render(&module, &context)?;
println!("{}", html);

Ok(())
}

Engine Creation

Memory Cache

For development and small applications:

use luat::{Engine, FileSystemResolver};

let resolver = FileSystemResolver::new("./templates");
let engine = Engine::with_memory_cache(resolver, 100)?;

The second parameter is the maximum number of compiled templates to cache.

Filesystem Cache

For production with persistent caching:

use luat::{Engine, FileSystemResolver};

let resolver = FileSystemResolver::new("./templates");
let engine = Engine::with_filesystem_cache(resolver, "./cache")?;

Compiled templates are stored in the specified cache directory.

Custom Cache

Implement your own caching strategy:

use luat::{Engine, FileSystemResolver, Cache};

struct MyCache { /* ... */ }

impl Cache for MyCache {
// Implement cache methods
}

let resolver = FileSystemResolver::new("./templates");
let cache = Box::new(MyCache::new());
let engine = Engine::new(resolver, cache)?;

Resolvers

FileSystemResolver

Loads templates from a directory:

use luat::FileSystemResolver;

let resolver = FileSystemResolver::new("./templates");

// Templates are resolved relative to the base path:
// "hello.luat" → "./templates/hello.luat"
// "components/Card.luat" → "./templates/components/Card.luat"

MemoryResourceResolver

For in-memory templates (testing, dynamic content):

use luat::MemoryResourceResolver;

let mut resolver = MemoryResourceResolver::new();

// Add templates programmatically
resolver.add_template("hello.luat", r#"
<h1>Hello, {props.name}!</h1>
"#.to_string());

resolver.add_template("Card.luat", r#"
<div class="card">
<h2>{props.title}</h2>
{@render props.children?.()}
</div>
"#.to_string());

// Remove templates
resolver.remove_template("hello.luat");

// Clear all templates
resolver.clear();

Custom Resolver

Implement ResourceResolver for custom loading:

use luat::ResourceResolver;

struct DatabaseResolver {
db: Database,
}

impl ResourceResolver for DatabaseResolver {
fn resolve(&self, path: &str) -> Result<String, Error> {
self.db.get_template(path)
}

fn exists(&self, path: &str) -> bool {
self.db.template_exists(path)
}
}

Rendering Methods

Compile and Render

For templates you'll render multiple times:

// Compile once
let module = engine.compile_entry("page.luat")?;

// Render multiple times with different contexts
for user in users {
let context = engine.to_value(&user)?;
let html = engine.render(&module, &context)?;
// Use html...
}

Render Source Directly

For one-off templates:

use std::collections::HashMap;

let source = "<h1>Hello, {props.name}!</h1>";
let mut context = HashMap::new();
context.insert("name".to_string(), serde_json::json!("World"));

let html = engine.render_source(source, &context)?;

Production Bundles

For production deployments, pre-compile templates into bundles.

Creating a Bundle

// Collect all template sources
let sources = vec![
("index.luat".to_string(), index_source),
("Card.luat".to_string(), card_source),
("Layout.luat".to_string(), layout_source),
];

// Create Lua source bundle
let lua_code = engine.bundle_sources(sources, |progress| {
println!("Bundling: {}", progress);
})?;

// Optionally compile to bytecode
let bytecode = engine.compile_bundle(&lua_code)?;

// Save to file
std::fs::write("bundle.bin", bytecode)?;

Loading a Bundle

// Load from Lua source
let lua_code = std::fs::read_to_string("bundle.lua")?;
engine.preload_bundle_code(&lua_code)?;

// Or load from bytecode
let bytecode = std::fs::read("bundle.bin")?;
engine.preload_bundle_code_from_binary(&bytecode)?;

// Render from bundle
let context = engine.to_value(json!({ "title": "Home" }))?;
let html = engine.render_from_bundle("index", &context).await?;

Context Helpers

Creating Context from Rust Types

use serde::Serialize;

#[derive(Serialize)]
struct PageData {
title: String,
items: Vec<Item>,
}

let data = PageData {
title: "My Page".to_string(),
items: vec![/* ... */],
};

let context = engine.to_value(&data)?;

Creating Lua Tables

use std::collections::HashMap;

// From HashMap
let mut map = HashMap::new();
map.insert("key", engine.lua().create_string("value")?);
let table = engine.create_table_from_hashmap(map)?;

// From Vec
let items = vec!["a", "b", "c"];
let table = engine.create_table_from_vec(items)?;

Cache Management

// Check if a template is cached
if engine.cache_contains("page.luat") {
println!("Template is cached");
}

// Clear all cached templates
engine.clear_cache()?;

Integration Example

Using Luat with Axum:

use axum::{routing::get, Router, response::Html};
use luat::{Engine, FileSystemResolver};
use std::sync::Arc;

struct AppState {
engine: Engine<FileSystemResolver>,
}

async fn home(state: Arc<AppState>) -> Html<String> {
let module = state.engine.compile_entry("+page.luat").unwrap();
let context = state.engine.to_value(serde_json::json!({
"title": "Home"
})).unwrap();

let html = state.engine.render(&module, &context).unwrap();
Html(html)
}

#[tokio::main]
async fn main() {
let resolver = FileSystemResolver::new("./src/routes");
let engine = Engine::with_memory_cache(resolver, 100).unwrap();

let state = Arc::new(AppState { engine });

let app = Router::new()
.route("/", get(home))
.with_state(state);

axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}

Error Handling

use luat::Error;

match engine.compile_entry("page.luat") {
Ok(module) => {
// Use module
}
Err(Error::TemplateNotFound(path)) => {
eprintln!("Template not found: {}", path);
}
Err(Error::ParseError(msg)) => {
eprintln!("Parse error: {}", msg);
}
Err(Error::RenderError(msg)) => {
eprintln!("Render error: {}", msg);
}
Err(e) => {
eprintln!("Error: {}", e);
}
}