Menu
Public repository / 91bd2a37654909c5a3aef6ddd614991bd378ccb8
AkurAI-Framework / site/content/docs/assets.md
Latest change e888012a38cfe0d4c47ddf4b3eecfba5ad6b8ac1 - Wire CSS engine + asset pipeline into serve/dev by Ólafur Búi Ólafsson
# Asset pipeline: fingerprinting & minification
Static assets — CSS, JS, images — want two things before they ship: a
**cache-busting name** so browsers can cache them forever, and **fewer bytes** so
they download fast. The `akurai-assets` crate does both in pure Rust, zero
dependencies, correctness first.
## Fingerprinting (cache-busting)
A *fingerprint* is a short content hash of an asset's bytes. Same bytes → same
hash; change a single byte → a different hash. Fold that hash into the filename
and the URL itself becomes the cache key:
```rust
use akurai_assets::{fingerprint, hashed_name};
let bytes = b".box{color:red}";
let hash = fingerprint(bytes); // e.g. "9a1c0f3e7b2d8c41" (16 hex chars)
let name = hashed_name("app.css", bytes); // "app.9a1c0f3e7b2d8c41.css"
```
The hash is inserted **before the extension** (`app.css` → `app.<hash>.css`);
multi-dot names keep their stem (`vendor.min.js` → `vendor.min.<hash>.js`) and
extensionless names just get it appended (`LICENSE` → `LICENSE.<hash>`).
Because the name changes whenever the content changes, a fingerprinted file can
be served with an **immutable, far-future cache header** and never re-validated:
```
Cache-Control: public, max-age=31536000, immutable
```
The hash is a 64-bit [FNV-1a](https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function)
digest rendered as lowercase hex. It is **not cryptographic** and does not need
to be — cache-busting only needs determinism and good change-sensitivity, not
collision resistance against an adversary.
## Minification (fewer bytes)
Both minifiers are **conservative**: they only remove bytes that are provably
insignificant, and they never reorder, rename, or rewrite code. Correctness beats
ratio.
### `minify_css`
```rust
let out = akurai_assets::minify_css(".box {\n margin: 0 24px 60px;\n}");
// ".box{margin:0 24px 60px}"
```
- Strips `/* … */` comments.
- Collapses whitespace runs and drops the space where it's redundant (next to
`{ } : ; ,` or at the edges).
- Drops the optional `;` immediately before `}`.
- **Never touches values:** the spaces inside multi-value declarations like
`0 24px 60px` shadows are preserved, and the contents of string literals and
`url(…)` (including `data:` URIs with their own `;`, `,`, `:`) pass through
byte for byte.
### `minify_js`
```rust
let out = akurai_assets::minify_js("let a = 1; // a comment");
// "let a = 1;"
```
A deliberately minimal pass:
- Strips `// line` and `/* block */` comments.
- Trims trailing whitespace and drops blank lines.
- Does **not** join lines or collapse intra-line whitespace, so Automatic
Semicolon Insertion is unaffected.
- String and template-literal contents are copied verbatim, so a `//` inside a
string (e.g. a URL) is preserved.
**Limits (by design):** no regex-literal awareness and no `${…}` interpolation
parsing — full JS minification is risky, so this stays a safe whitespace/comment
pass. For aggressive minification, pre-minify upstream and let this layer only
fingerprint.
## How the CLI uses it
The pipeline is wired into `akurai serve`. On startup the server scans every
static asset under your frontend dir (anything with a non-`.html` extension),
conservatively minifies CSS and JS (other types pass through byte-for-byte),
content-hashes the **processed** bytes, and builds an in-memory manifest mapping
each plain path to its fingerprinted name. Nothing is written to disk and your
source files are never modified. The generated `utilities.css` (see
[Styling](/docs/styling)) is folded into the same manifest.
### Serving rules (backward compatible)
- A request for a **fingerprinted** path the manifest knows
(`/styles.<hash>.css`) is served from memory with the processed (minified)
bytes, the correct `Content-Type`, and
`Cache-Control: public, max-age=31536000, immutable` — the URL itself is the
cache key, so the response can be cached forever.
- A request for the **plain** path (`/styles.css`) resolves exactly as before:
the raw file is read off disk with no immutable header. A project that never
references a fingerprinted URL is completely unaffected.
- The manifest (plain ⇒ hashed name) is exposed as JSON at **`/api/_assets`** so
a page, template, or tool can discover the fingerprinted URLs to link.
### Discovering hashed URLs
```
GET /api/_assets
{ "/styles.css": "/styles.9a1c0f3e7b2d8c41.css",
"/app.js": "/app.b4d2….js",
"/utilities.css": "/utilities.….css" }
```
Link the value to opt a given asset into immutable caching; link the plain path
to keep the current (revalidated) behavior.
### Dev mode
Under `akurai dev` the pipeline is **disabled** (an empty manifest): dev wants
fresh, un-hashed assets and live-reload, not far-future caching. `/api/_assets`
returns `{}` and every asset is served plainly off disk.
## Status
Shipped: fingerprinting, both minifiers (`akurai-assets`), and the `crates/cli`
wiring above (startup manifest, immutable cache headers on fingerprinted
requests, `/api/_assets`).
**Deferred (by design):** the server does **not** rewrite references inside
served HTML to the hashed URLs — automatic rewriting of arbitrary template
output is risky, so opting an asset into immutable caching is an explicit choice
(link the `/api/_assets` value). Minification stays the conservative
whitespace/comment pass documented above; aggressive transforms are out of
scope.