# How to Use the asset! Macro for Static File Bundling and Caching in Topcoat

> Learn to use the asset! macro in Topcoat for efficient static file bundling and caching. Embed files directly into your executable for content-hashed, cache-friendly assets.

- Repository: [Tokio/topcoat](https://github.com/tokio-rs/topcoat)
- Tags: how-to-guide
- Published: 2026-07-31

---

**The `asset!` macro embeds a compile-time binary descriptor into your executable that the Topcoat bundler extracts to create content-hashed, cache-friendly static files served under `/_topcoat/assets`.**

The `asset!` macro in the tokio-rs/topcoat repository transforms static files and remote URLs into type-safe, zero-cost handles that are bundled and cached at build time. Unlike traditional file serving that relies on runtime filesystem access, Topcoat's approach embeds asset metadata directly into your binary through the `RawAsset` encoding system, enabling deterministic content hashing and immutable CDN deployments.

## Understanding the asset! Macro Architecture

The `asset!` macro expands into three components at compile time: a constant `Asset` handle, an `EncodedAsset` byte array, and a pre-computed `AssetId`. This architecture ensures assets cannot be dead-code eliminated while keeping the runtime handle lightweight.

### Asset and AssetId

In [`crates/topcoat-asset/src/asset.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-asset/src/asset.rs) (lines 17-27), the **Asset** struct defines a thin handle containing the embedded byte slice and providing an `id()` method to retrieve the stable **AssetId**. The **AssetId** (lines 78-90) is a deterministic 64-bit hash derived from the crate name, source file path, asset path, and options, guaranteeing stability across builds when declarations remain unchanged.

### RawAsset Encoding

The `RawAsset::encode` and `RawAsset::decode` functions (lines 31-41 and 44-55) manage a fixed-size 2048-byte binary payload containing the ID, path, crate context, and options. The bundler locates these declarations by scanning the binary for a unique scrambled prefix, allowing it to extract every asset definition without parsing source code.

### The Bundler

The **Bundler** implementation in [`crates/topcoat-asset/src/bundler.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-asset/src/bundler.rs) walks the compiled binary, finds every encoded asset descriptor, resolves source paths (local or remote), copies or downloads files into the bundle directory, and produces a [`manifest.toml`](https://github.com/tokio-rs/topcoat/blob/main/manifest.toml) mapping `AssetId` to bundled filenames. This process runs via the `topcoat asset bundle` command.

## Declaring Assets with the asset! Macro

### Local File Paths

Local paths resolve relative to the declaration context:

- `./` or `../` → relative to the source file containing the `asset!` call
- Other relative paths → anchored to `CARGO_MANIFEST_DIR`
- Absolute paths → used verbatim

```rust
use topcoat::{asset::{Asset, asset}, view::view};

// Relative to CARGO_MANIFEST_DIR
const LOGO: Asset = asset!("assets/logo.png");

// Relative to this source file (src/ui/header.rs)
const HEADER_BG: Asset = asset!("./images/header.jpg");

```

### Remote URLs

Remote assets specified with `http://` or `https://` are downloaded once during bundling and cached in `<target>/topcoat/cache/assets` for subsequent builds. The bundler treats cached downloads as local files after initial retrieval.

```rust
const DATATABLE_JS: Asset = asset!(
    "https://cdn.jsdelivr.net/npm/datatable@1.10.24/datatable.min.js"
);

```

### Asset Options

The macro accepts optional named arguments compiled into the binary descriptor:

- **`rename: "name"`** → Replaces the file stem in the bundled filename
- **`extension: "ext"`** → Forces a specific extension for files without one
- **`checksum: "sha256:<hex>"`** → Verifies source integrity; fails the bundle if mismatched
- **`content_type: "mime/type"`** → Overrides automatic MIME type detection

```rust
const VERIFIED_JS: Asset = asset!(
    "https://cdn.example.com/lib.js",
    rename: "library",
    checksum: "sha256:9f5b2c3d4e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
    content_type: "application/javascript"
);

```

## The Bundling Workflow

The end-to-end asset pipeline follows four stages:

1. **Declaration** → Developers call `asset!` anywhere in the codebase, creating embedded descriptors in [`crates/topcoat-asset/src/asset.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-asset/src/asset.rs) (lines 63-66)
2. **Compilation** → The Rust compiler embeds the `RawAsset` binary descriptors into the final executable
3. **Bundling** → Running `topcoat asset bundle` scans the binary, resolves all paths, downloads remote files, computes content hashes, and writes files to `<target>/assets` with hashed names like `logo-1a2b3c4d5e6f7a8b.png`
4. **Verification** → The bundler generates [`manifest.toml`](https://github.com/tokio-rs/topcoat/blob/main/manifest.toml) mapping `AssetId` values to content-hashed filenames, ensuring the bundle matches the exact binary that produced it

## Serving Assets in Production

### Local Serving

Attach the bundle to your router using `RouterBuilderAssetExt::assets`, which registers the route prefix `/_topcoat/assets` and makes each `Asset` render as its public URL.

```rust
use topcoat::{
    asset::{AssetBundle, RouterBuilderAssetExt},
    router::{Router, RouterBuilderDiscoverExt},
};

pub fn router() -> Router {
    Router::builder()
        .discover()
        .assets(AssetBundle::load().unwrap())
        .build()
}

```

If the bundle resides outside the executable directory, use `AssetBundle::load_dir("dist/assets")` to specify the path.

### CDN Hosting

For external hosting, use `AssetConfig::hosted_at` to generate URLs pointing to your CDN while still loading the local bundle for ID-to-filename resolution.

```rust
use topcoat::{
    asset::{AssetBundle, AssetConfig, RouterBuilderAssetExt},
    router::Router,
};

let router = Router::builder()
    .assets(AssetConfig::hosted_at(
        "https://cdn.example.com/assets",
        AssetBundle::load().unwrap(),
    ))
    .build();

```

This configuration emits URLs like `https://cdn.example.com/assets/logo-1a2b3c4d5e6f7a8b.png`. You must manually upload the bundle files from `<target>/assets` to your CDN.

## Summary

- The **`asset!` macro** creates compile-time asset descriptors that survive dead-code elimination and provide zero-cost runtime handles.
- **Path resolution** supports relative paths (to source file or manifest), absolute paths, and remote URLs with local caching.
- **Asset options** enable renaming, extension overrides, SHA-256 verification, and content-type specification.
- The **Bundler** extracts descriptors from the binary, hashes content, and produces cache-friendly filenames.
- **Router integration** via `RouterBuilderAssetExt::assets` serves files locally or generates CDN URLs while maintaining the `AssetId` mapping.

## Frequently Asked Questions

### What happens if I deploy a new binary without updating the asset bundle?

The application will panic at runtime when attempting to render an asset. Because `AssetId` values are derived from the declaration location and options in [`crates/topcoat-asset/src/asset.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-asset/src/asset.rs) (lines 78-90), the binary and bundle share a strict coupling. This early failure prevents broken image links or stale JavaScript in production.

### How does Topcoat handle cache invalidation for static assets?

The bundler automatically embeds a content hash into every filename (e.g., [`app-5e6f7a8b9c0d1e2f.css`](https://github.com/tokio-rs/topcoat/blob/main/app-5e6f7a8b9c0d1e2f.css)). Since the hash changes only when the file contents change, you can serve these assets with immutable, long-lived HTTP cache headers. When content changes, the `AssetId` maps to a new hashed filename, forcing clients to fetch the updated version.

### Can I use the asset! macro with files outside my crate directory?

Yes, but with limitations. Absolute paths are used verbatim, and relative paths resolve to `CARGO_MANIFEST_DIR`. However, for reproducible builds and team development, it is recommended to keep assets within the crate directory or use remote URLs for external dependencies. The bundler caches remote downloads in `<target>/topcoat/cache/assets` to avoid repeated network requests.

### What is the performance impact of using many asset! declarations?

Runtime performance impact is zero. Each `Asset` is a const handle containing a static byte slice reference. The compile-time binary size increases by 2048 bytes per asset (the `RawAsset` descriptor size), and the bundler scans this embedded metadata during the build process, not at runtime. Memory overhead consists only of the `Asset` handle (typically a pointer and length) when used in views.