fs-embed 0.1.0

Embed files in release, read from disk in debug β€” with a unified API.
Documentation

fs-embed

Embed directories into your binary at compile time with zero runtime config β€” supporting both embedded and live (dynamic) modes through a single API.


πŸ“ Philosophy

fs-embed is built for frictionless file access across environments:

  • βœ… Always embedded: fs_embed!("dir") embeds directories at compile time, even in debug builds.
  • πŸ” Switch to live mode (e.g. for hot-reloading): Call .into_dynamic() or .auto_dynamic() β€” no need to change your file access logic.
  • πŸ”— Overlay support: Compose multiple embedded or dynamic directories using DirSet, with override precedence.
  • πŸ“© One macro, one API: No cargo features, no env vars, no conditional compilation.

This enables:

  • Reproducible builds: Assets are fully embedded in release binaries.
  • Fast iteration in development: Read live files with a one-line change β€” no config, no rebuild.
  • Clean architecture: Keep templates, config, and assets in sync with source and override them per environment.

πŸš€ Quick Start

use fs_embed::fs_embed;

// Embed the "static" folder β€” always at compile time, even in debug builds.
static STATIC: fs_embed::Dir = fs_embed!("static");

fn main() {
    // Use disk in debug builds, embedded in release
    let dir = STATIC.auto_dynamic();

    // Load a file (relative to the embedded root or disk path)
    if let Some(file) = dir.get_file("css/style.css") {
        let content = file.read_str().unwrap();
        println!("{content}");
    }
}

πŸ“Œ fs_embed!("static") expects a literal relative path inside your crate, resolved via CARGO_MANIFEST_DIR.


πŸ”„ Embedded vs Dynamic

Two methods let you switch modes at runtime:

  • .into_dynamic()

    • Always reads from disk.
    • Useful for tests, hot-reload, or CLI tools.
    let dir = fs_embed!("templates").into_dynamic();
    
  • .auto_dynamic()

    • Reads from disk in debug, embedded in release.
    • Best for development–production parity.
    let dir = fs_embed!("templates").auto_dynamic();
    

πŸ“š Core API

Method Description
Dir::get_file(path) Get a file by relative path
Dir::read_str() / read_bytes() Read file contents
Dir::walk() Recursively yield all files
Dir::walk_override() Recursively yield highest-precedence files (for overlays)
Dir::is_embedded() Returns true if directory is embedded
Dir::into_dynamic() Convert to dynamic (disk-backed) mode
Dir::auto_dynamic() Use disk in debug, embedded in release

πŸ₯ƒ Override Behavior

You can compose multiple directories using DirSet:

let dir = DirSet::new([
    fs_embed!("base"),
    fs_embed!("theme").auto_dynamic(),
]);

let file = dir.get_file("index.html"); // From theme if it exists, otherwise base
  • Overlay precedence is left-to-right.
  • Only the first match for each path is returned.
  • walk_override() yields exactly one file per unique relative path.

⚠️ Notes

  • DirSet::walk() traversal order is not guaranteed (OS-dependent).
  • Dir::entries() preserves insertion order of embedded files and folders, but disk-backed order may vary.
  • All embedded files are resolved at compile time. Dynamic mode reads directly from disk at runtime.

πŸ“¦ Installation

[dependencies]
fs-embed = "0.1"

πŸ“– Docs

Full API documentation: docs.rs/fs-embed


❀️ Inspired by

fs-embed unifies compile-time embedding with runtime overrides in a zero-config, ergonomic interface β€” ideal for CLI tools, embedded apps, web frameworks, and static-site generators.