AkurAI Build
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.