Hauchiwa Docs

Asset pipeline

Hauchiwa comes batteries-included with a powerful asset pipeline. It treats assets as first-class citizens in the graph.

Images

Hauchiwa can automatically resize and convert images to modern formats.

// Returns Many<Image>
let images = config.load_images()
    .glob("assets/images/*.jpg")?
    .glob("assets/images/*.png")?
    .format(ImageFormat::WebP)
    .register();

Styling (CSS/Sass)

We use grass, a high-performance Sass compiler written in Rust.

// Returns Many<Stylesheet>
let css = config.load_css()
    .entry("assets/style.scss")?
    .watch("assets/**/*.scss")? // Watch imports for changes
    .minify(true)
    .register();

Hauchiwa hashes the output filename (e.g., a1b2c3d4e5f6.css) for perfect long-term caching.

Static files

Use Blueprint::copy_static to copy an entire directory tree into the output directory as-is. Files whose content has not changed are skipped (mtime + BLAKE3 fallback), so repeated builds stay fast.

let config = Blueprint::<()>::new()
    .copy_static("assets/fonts", "fonts")   // dist/fonts/  ← assets/fonts/
    .copy_static("assets/icons", "icons");  // dist/icons/  ← assets/icons/

The destination path is validated to stay inside the output directory - relative traversals like ../../etc are rejected with an error at build time.

Scripts (JS & Svelte)

JavaScript / TypeScript

Hauchiwa uses esbuild for blazingly fast bundling. It supports TypeScript out of the box.

let js = config.load_esbuild()
    .entry("src/client.ts")?
    .bundle(true)
    .minify(true)
    .register();

Use .external() to mark an npm package as external. Hauchiwa will bundle it separately and register it in the Import Map so the browser resolves it via a bare specifier:

let js = config.load_esbuild()
    .entry("src/app.ts")?
    .external("react")
    .external("react-dom")
    .register();

Svelte integration (SSR + hydration)

Requires: deno binary on your system PATH.

This is one of Hauchiwa's superpower features. It orchestrates a hybrid rendering pipeline using Deno.

  1. Server-side rendering (SSR): Components are compiled to runs on the server (in Rust via Deno) to generate static HTML.
  2. Hydration: A lightweight client-side script is generated to "wake up" the component in the browser.
#[derive(Clone, Serialize, Deserialize)]
struct CounterProps {
    start: i32,
}

// 1. Load Component
let counters = config.load_svelte::<CounterProps>()
    .entry("components/Counter.svelte")?
    .register();

// 2. Render in Task
config.task().using(counters).merge(|ctx, counters| {
    let component = counters.get("components/Counter.svelte").unwrap();
    
    // Render static HTML
    let html = (component.prerender)(&CounterProps { start: 10 })?;
    
    // 'component.hydration' points to the JS file needed for the browser
    println!("HTML: {}", html);
    Ok(())
});

Import maps

Hauchiwa automatically generates an Import Map, resolving bare specifiers like "svelte" or to their correct, hashed locations in the final build. It just needs to be included in your HTML <head>.

Templates (Minijinja)

Enable the minijinja feature to load Jinja2-style templates as a coarse-grained dependency in your graph.

hauchiwa = { version = "*", features = ["minijinja"] }
use hauchiwa::loader::TemplateEnv;

// Returns One<TemplateEnv>
let templates = config.load_minijinja()
    .glob("templates/**/*.html")?
    .register();

config.task().using(templates).merge(|ctx, env| {
    let tmpl = env.get_template("base.html")?;
    let html = tmpl.render(hauchiwa::minijinja::context! { title => "Hello" })?;
    Ok(vec![Output::html("index", html)])
});

Use .filter() to register custom Jinja filters before the environment is built:

let templates = config.load_minijinja()
    .glob("templates/**/*.html")?
    .filter("shout", |s: String| s.to_uppercase())
    .register();

Any change to a watched template file causes the loader to re-execute and all dependent tasks to re-run.

Search

Hauchiwa integrates with pagefind to generate static search indexes.

config.use_pagefind()
    .index(pages_a) // One<Vec<Page>>
    .index(pages_b) // One<Vec<Page>>
    .register();