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 installfrom crates.io when the published crate includes only the dist directory - Dev-proxy mode (
dev-proxyfeature + 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:
- Whether the
dev-proxycargo feature is enabled - Whether
package.jsonexists in the frontend project directory (i.e., source is available)
dev-proxy feature | package.json exists | Mode |
|---|---|---|
| enabled | yes | Dev-proxy |
| enabled | no | Prebuilt (fallback) |
| disabled | yes | Build |
| disabled | no | Prebuilt |
§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 --releaseYou 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",
)| Argument | Description |
|---|---|
path | Path to the frontend project directory (required) |
build | Build command override (default: auto-detected from framework) |
dist | Dist 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 file | Package manager | Run prefix |
|---|---|---|
bun.lockb / bun.lock | Bun | bun run |
pnpm-lock.yaml | pnpm | pnpm exec |
yarn.lock | Yarn | yarn exec |
package-lock.json | npm | npx |
Framework (detected by config file):
| Config file | Framework | Dev command | Build command | Dist dir |
|---|---|---|---|---|
vite.config.{js,ts,mjs} | Vite | vite --strictPort --clearScreen false | vite build | dist |
webpack.config.{js,ts,mjs} | Webpack | webpack serve | webpack build | dist |
next.config.{js,ts,mjs} | Next.js | next dev | next 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:
- Build your frontend assets (e.g.,
cd client && npm run build) - 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 publishModules§
- build
- Build-script helper for tracking frontend sources on stable Rust.
Macros§
- frontend
- Build a
FrontendHandlerfor your frontend project.