Skip to main content

Crate trillium_frontend

Crate trillium_frontend 

Source
Expand description

A Trillium handler for serving JS frontend projects with three operating modes:

  • Build mode (default, source available): runs the frontend build at compile time and embeds all dist assets directly in the binary via trillium_static_compiled
  • Prebuilt mode (default, no source): embeds pre-built dist assets without attempting to build — this is what happens during cargo install from crates.io when the published crate includes only the dist directory
  • Dev-proxy mode (dev-proxy feature + source available): auto-detects your framework and package manager, spawns the dev server on a free port, and proxies all requests to it — including WebSocket upgrades for HMR

§Mode selection

The mode is determined by two factors:

  1. Whether the dev-proxy cargo feature is enabled
  2. Whether package.json exists in the frontend project directory (i.e., source is available)
dev-proxy featurepackage.json existsMode
enabledyesDev-proxy
enablednoPrebuilt (fallback)
disabledyesBuild
disablednoPrebuilt

§Usage

ⓘ
use trillium_client::Client;
use trillium_frontend::frontend;
use trillium_smol::ClientConfig;

fn main() {
    trillium_smol::run((
        frontend!("./client")
            .with_client(Client::new(ClientConfig::default()))
            .with_index_file("index.html"),
    ));
}

The same code works in all three modes — .with_client() is accepted but ignored when the dev-proxy feature is not enabled.

§Enabling dev-proxy mode

Pass the feature flag when you want live-reloading during development:

# Development: live-reloading proxy
cargo run --features trillium-frontend/dev-proxy

# Production: build and embed assets at compile time
cargo build --release

You can also define a feature in your own crate that forwards it, for brevity:

[features]
dev-proxy = ["trillium-frontend/dev-proxy"]
cargo run --features dev-proxy

§Macro syntax

ⓘ
// Simple: path is relative to the Cargo.toml of the calling crate
frontend!("./client")

// With explicit overrides
frontend!(
    path = "./client",
    build = "bun run build",
    dist = "dist",
)
ArgumentDescription
pathPath to the frontend project directory (required)
buildBuild command override (default: auto-detected from framework)
distDist directory name relative to path (default: auto-detected, usually "dist")

§Auto-detection

When source is available (package.json exists), trillium-frontend inspects the project directory for known config and lock files to determine the build and dev commands.

Package manager (detected by lock file, in priority order):

Lock filePackage managerRun prefix
bun.lockb / bun.lockBunbun run
pnpm-lock.yamlpnpmpnpm exec
yarn.lockYarnyarn exec
package-lock.jsonnpmnpx

Framework (detected by config file):

Config fileFrameworkDev commandBuild commandDist dir
vite.config.{js,ts,mjs}Vitevite --strictPort --clearScreen falsevite builddist
webpack.config.{js,ts,mjs}Webpackwebpack servewebpack builddist
next.config.{js,ts,mjs}Next.jsnext devnext build.next

§Rebuilding when frontend sources change

In build mode the frontend build runs at compile time, so cargo must treat your JS/TS sources as compile inputs — otherwise editing only a frontend file and re-running cargo build can leave the previously embedded assets in place. On nightly this is handled automatically (the macro registers source files via the unstable proc_macro_tracked_path feature, detected at build time — nothing embedded, no setup). On stable, proc macros can’t declare file dependencies, so a frontend-only edit may be missed until another recompile is triggered. If you develop or build on stable, or want belt-and-suspenders insurance on any toolchain, add the optional build-script shim — it is not essential. Dev-proxy mode needs none of this (HMR handles it).

§Publishing for cargo install

To support cargo install without requiring JS tooling on the user’s machine:

  1. Build your frontend assets (e.g., cd client && npm run build)
  2. Include the dist directory in your published crate, but exclude package.json

The absence of package.json tells trillium-frontend to use the pre-built assets as-is without attempting to run a build command.

[package]
include = [
    "src/",
    "client/dist/",   # pre-built assets to embed
    # Do NOT include client/package.json — its absence triggers prebuilt mode
]

Then publish with:

cargo build --release  # triggers the frontend build via the proc macro
cargo publish

Modules§

build
Build-script helper for tracking frontend sources on stable Rust.

Macros§

frontend
Build a FrontendHandler for your frontend project.

Structs§

FrontendHandler