# How llmfit Embeds the React dist/ Folder into the Binary via include_str! in the Build Script

> Discover how llmfit embeds the React dist folder into its binary using include_str! in the build script. Learn about static byte slices and compiled Rust modules for your executable.

- Repository: [Alex Jones/llmfit](https://github.com/AlexsJones/llmfit)
- Tags: internals
- Published: 2026-09-12

---

**The `llmfit-tui` crate uses a custom Cargo build script to traverse the `llmfit-web/dist` directory and generate a Rust module containing `include_bytes!` (and `include_str!` for text assets) macros, compiling the entire React dashboard directly into the final executable as static byte slices.**

The AlexsJones/llmfit repository solves the challenge of shipping a web-based UI with a Rust CLI by embedding the compiled React frontend directly into the binary. At compile time, the [`llmfit-tui/build.rs`](https://github.com/AlexsJones/llmfit/blob/main/llmfit-tui/build.rs) script locates the build output from the `llmfit-web` package and generates static asset modules that become part of the final executable, eliminating runtime file system dependencies.

## Locating the Web Assets Relative to the Crate

The build script begins by resolving the path to the React build output relative to the `llmfit-tui` crate root. Since the `llmfit-web` package typically sits at the workspace root, the script constructs a path pointing to `../llmfit-web/dist` from the `CARGO_MANIFEST_DIR`.

```rust
// llmfit-tui/build.rs
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let dist_dir = Path::new(&manifest_dir)
    .join("..")
    .join("llmfit-web")
    .join("dist");

```

This approach ensures the build script can locate the minified JavaScript, CSS, and HTML files regardless of where the build command is invoked from within the workspace.

## Detecting Changes with cargo:rerun-if-changed

To ensure the binary rebuilds whenever the React frontend changes, the script emits a **rerun-if-changed** directive for the entire `dist/` directory. This Cargo instruction monitors all files recursively within the folder.

```rust
// llmfit-tui/build.rs
println!("cargo:rerun-if-changed={}", dist_dir.display());

```

When any file in `llmfit-web/dist` is modified, Cargo automatically re-executes the build script before the next compilation, keeping the embedded assets synchronized with the latest web build.

## Generating Static Asset Modules with include_bytes! and include_str!

The core embedding mechanism involves generating a temporary Rust source file—[`generated_assets.rs`](https://github.com/AlexsJones/llmfit/blob/main/generated_assets.rs)—that contains static references to each file in the `dist/` folder. The build script walks the directory tree and creates a `static` byte slice for each asset using **include_bytes!** for binary files (images, fonts) and **include_str!** for text files (HTML, JS, CSS).

```rust
// llmfit-tui/build.rs (simplified generation logic)
fn generate_assets_from_dist(dist_path: &Path, files: &[PathBuf]) -> String {
    let mut output = String::new();
    
    for file in files {
        let relative_path = file.strip_prefix(dist_path).unwrap();
        let var_name = path_to_var_name(relative_path);
        
        if is_text_file(file) {
            // Use include_str! for HTML, CSS, and JS files
            output.push_str(&format!(
                "pub const {}: &str = include_str!(r#\"{}\"#);\n",
                var_name,
                file.display()
            ));
        } else {
            // Use include_bytes! for binary assets
            output.push_str(&format!(
                "pub const {}: &[u8] = include_bytes!(r#\"{}\"#);\n",
                var_name,
                file.display()
            ));
        }
    }
    output
}

```

The generated file is written to the source directory (typically [`src/generated_assets.rs`](https://github.com/AlexsJones/llmfit/blob/main/src/generated_assets.rs)) and then included in the main library via the **include!** macro or by declaring it as a module.

## Fallback Handling for Missing Web Builds

When the `dist/` directory does not exist—such as during a fresh clone where `npm run build` hasn't been executed—the build script implements a **fallback mechanism**. Instead of failing, it embeds a minimal placeholder page defined as a constant (`FALLBACK_JS` or similar) and emits a Cargo warning to alert the developer.

```rust
// llmfit-tui/build.rs
if !dist_dir.is_dir() {
    println!("cargo:warning=llmfit-web/dist not found. Using fallback page.");
    println!("cargo:warning=Run 'npm ci && npm run build' in llmfit-web/ to embed the dashboard.");
    
    // Generate fallback content
    let fallback = r#"<!DOCTYPE html><html><body>Dashboard not built</body></html>"#;
    std::fs::write("src/generated_assets.rs", format!(
        "pub const INDEX_HTML: &str = {:?};",
        fallback
    )).unwrap();
}

```

This ensures the Rust crate always compiles successfully, even without the Node.js build environment, while clearly indicating that the web UI is unavailable.

## Serving Embedded Assets at Runtime

At runtime, the HTTP server implemented in **[`llmfit-tui/src/serve_api.rs`](https://github.com/AlexsJones/llmfit/blob/main/llmfit-tui/src/serve_api.rs)** serves these embedded files directly from memory. Since the assets are compiled as `&'static [u8]` or `&'static str` references, they require no file system I/O and remain available even in containerized or restricted environments.

```rust
// llmfit-tui/src/serve_api.rs (conceptual)
use crate::generated_assets;

fn serve_static(path: &str) -> Response {
    match path {
        "/" => Response::html(generated_assets::INDEX_HTML),
        "/index.js" => Response::javascript(generated_assets::INDEX_JS),
        "/style.css" => Response::css(generated_assets::STYLE_CSS),
        _ => Response::not_found(),
    }
}

```

Because the assets are embedded at compile time, the resulting `llmfit` binary is completely self-contained, shipping both the TUI logic and the React dashboard in a single executable file.

## Summary

- The **[`llmfit-tui/build.rs`](https://github.com/AlexsJones/llmfit/blob/main/llmfit-tui/build.rs)** script automates the embedding of React build artifacts by generating a [`generated_assets.rs`](https://github.com/AlexsJones/llmfit/blob/main/generated_assets.rs) module at compile time.
- **include_bytes!** handles binary assets (images, fonts), while **include_str!** handles text-based assets (HTML, CSS, JavaScript), ensuring proper static typing for each file type.
- **`cargo:rerun-if-changed`** ensures the binary rebuilds automatically whenever files in `llmfit-web/dist` are modified.
- A **fallback mechanism** allows the crate to compile even when the web build is absent, emitting developer warnings instead of hard failures.
- The **[`serve_api.rs`](https://github.com/AlexsJones/llmfit/blob/main/serve_api.rs)** module serves these static slices directly from memory, creating a zero-dependency runtime deployment.

## Frequently Asked Questions

### What happens if the llmfit-web/dist folder is missing during compilation?

The build script detects the missing directory and falls back to a minimal placeholder page defined in the **FALLBACK_JS** constant or similar inline string. It prints a `cargo:warning` message instructing the developer to run `npm ci && npm run build` in the `llmfit-web/` directory, but the Rust compilation succeeds with limited functionality.

### How does the build script detect changes in the React frontend?

The script emits **`cargo:rerun-if-changed=${DIST_DIR}`** for the entire `dist/` directory path. Cargo monitors every file within this directory tree, triggering a rebuild of the `llmfit-tui` crate whenever any asset (HTML, JS, CSS) is modified, ensuring the embedded content stays synchronized with source changes.

### What is the difference between include_str! and include_bytes! in this embedding pattern?

**include_str!** embeds the file contents as a `&'static str`, suitable for UTF-8 text files like HTML, CSS, and JavaScript, allowing direct string manipulation. **include_bytes!** embeds content as a `&'static [u8]`, which is required for binary assets like images, fonts, or WebAssembly modules that may contain non-UTF-8 data. The `llmfit-tui` build script selects the appropriate macro based on the file extension.

### How are the embedded assets served at runtime without a filesystem?

The generated [`generated_assets.rs`](https://github.com/AlexsJones/llmfit/blob/main/generated_assets.rs) module creates compile-time constants that point directly to the embedded data in the binary's **read-only data section**. The [`serve_api.rs`](https://github.com/AlexsJones/llmfit/blob/main/serve_api.rs) HTTP handler matches incoming request paths to these constants and returns the static slices directly from memory, requiring no temporary files or disk access during execution.