85 lines
2.8 KiB
Rust
85 lines
2.8 KiB
Rust
use std::collections::HashMap;
|
|
use std::sync::LazyLock;
|
|
|
|
use sha2::{Digest, Sha256};
|
|
|
|
/// A single static asset: its embedded bytes and a content hash used to
|
|
/// version its URL.
|
|
pub struct StaticAsset {
|
|
pub data: &'static [u8],
|
|
pub hash: String,
|
|
}
|
|
|
|
/// A registry of the app's static assets, keyed by filename. Each asset's
|
|
/// content hash is computed once on first use and cached, so the versioned URL
|
|
/// changes automatically whenever the underlying file changes.
|
|
pub struct StaticAssetStore {
|
|
assets: HashMap<&'static str, StaticAsset>,
|
|
}
|
|
|
|
impl StaticAssetStore {
|
|
fn new(entries: &[(&'static str, &'static [u8])]) -> Self {
|
|
let assets = entries
|
|
.iter()
|
|
.map(|(name, data)| {
|
|
let hash = content_hash(data);
|
|
(*name, StaticAsset { data, hash })
|
|
})
|
|
.collect();
|
|
Self { assets }
|
|
}
|
|
|
|
/// Looks up an asset by its filename (e.g. `"style.css"`).
|
|
pub fn get(&self, name: &str) -> Option<&StaticAsset> {
|
|
self.assets.get(name)
|
|
}
|
|
|
|
/// Returns the versioned URL for an asset, e.g. `/static/style.css?v=<hash>`.
|
|
pub fn url(&self, name: &str) -> Option<String> {
|
|
self.assets
|
|
.get(name)
|
|
.map(|asset| format!("/static/{name}?v={}", asset.hash))
|
|
}
|
|
}
|
|
|
|
fn content_hash(data: &[u8]) -> String {
|
|
let mut hasher = Sha256::new();
|
|
hasher.update(data);
|
|
hex::encode(hasher.finalize())
|
|
}
|
|
|
|
/// Constructs a `StaticAssetStore` from `name => path` pairs, embedding each
|
|
/// file's bytes at compile time via `include_bytes!`.
|
|
macro_rules! static_assets {
|
|
($($name:literal => $path:literal),* $(,)?) => {
|
|
StaticAssetStore::new(&[
|
|
$(($name, include_bytes!($path))),*
|
|
])
|
|
};
|
|
}
|
|
|
|
/// The app's static assets, loaded once on first use.
|
|
pub static STORE: LazyLock<StaticAssetStore> = LazyLock::new(|| {
|
|
static_assets! {
|
|
"style.css" => "../static/style.css",
|
|
"passkey-login.js" => "../static/passkey-login.js",
|
|
"passkey-register.js" => "../static/passkey-register.js",
|
|
"password-toggle.js" => "../static/password-toggle.js",
|
|
"htmx.min.js" => "../static/htmx.min.js",
|
|
"idiomorph-ext.min.js" => "../static/idiomorph-ext.min.js",
|
|
"htmx-ws.min.js" => "../static/htmx-ws.min.js",
|
|
"favicon.ico" => "../static/favicon.ico",
|
|
"favicon-32x32.png" => "../static/favicon-32x32.png",
|
|
"apple-touch-icon.png" => "../static/apple-touch-icon.png",
|
|
"logo.svg" => "../static/logo.svg",
|
|
}
|
|
});
|
|
|
|
/// Returns the versioned URL for a known asset, panicking if the name isn't
|
|
/// registered (a programmer error, since these are compile-time constants).
|
|
pub fn url(name: &str) -> String {
|
|
STORE
|
|
.url(name)
|
|
.unwrap_or_else(|| panic!("unknown static asset: {name}"))
|
|
}
|