#web-apps #compile-time #static #assets #cache #applications #busting

cacheb

Compile time cache busting for static assets in web applications

1 unstable release

new 0.1.0 Mar 16, 2025

#694 in Development tools

MIT license

16KB
339 lines

cacheb

Compile time cache busting for static assets in web applications.

Features

  • Content-based hashes ensure browsers load the latest version
  • Seamless integration with templates
  • Files are organized in modules matching your directories
  • Compiler catches typos in file references

Example usage

Add cacheb to [build-dependencies].

[build-dependencies]
cacheb = "0.1"

Create a build.rs to trigger cacheb whenever your static files change.

fn main() {
    println!("cargo:rerun-if-changed=static/");
    cacheb::codegen(
        &PathBuf::from("src/static_assets.rs"),
        &[PathBuf::from("static")],
        &[]
    ).unwrap();
}

Reference your static assets in templates (eg. Maud) with automatic cache busting:

mod static_assets;
use static_assets::*;

fn page() -> maud::Markup {
    html! {
        head {
            link rel="stylesheet" href=(styles::main_css);
            link rel="icon" href=(favicon_ico);
        }
        body {
            img src=(images::logo_png) alt="Logo";
            script src=(scripts::app_js) {}
        }
    }
}

Implement a handler to serve your static files:

#[derive(TypedPath, Deserialize)]
#[typed_path("/static/{*path}")]
pub struct StaticFilePath {
    pub path: String,
}

pub async fn static_path(StaticFilePath { path }: StaticFilePath) -> impl IntoResponse {
    let data = StaticFile::get(&format!("/static/{}", path));

    if let Some(data) = data {
        let file = match tokio::fs::File::open(data.file_name).await {
            Ok(file) => file,
            Err(_) => {
                return Response::builder()
                    .status(StatusCode::NOT_FOUND)
                    .body(Body::empty())
                    .unwrap();
            }
        };

        return Response::builder()
            .status(StatusCode::OK)
            .header(
                header::CONTENT_TYPE,
                HeaderValue::from_str(data.mime.as_ref()).unwrap(),
            )
            .body(Body::from_stream(ReaderStream::new(file)))
            .unwrap();
    }

    Response::builder()
        .status(StatusCode::NOT_FOUND)
        .body(Body::empty())
        .unwrap()
}

let app = Router::new()
    .route("/static/{*path}", get(static_path));

Dependencies

~18KB