Molt
High-Performance Rust Web Framework
Fast, type-safe HTML generation for Rust โ and for the browser, through WebAssembly.
๐ฆ molt-framework on crates.io ยท ๐ docs.rs
A tree of plain values, escaped by default, rendered compact or pretty. The output is checked byte for byte against golden fixtures.
Today Molt is the HTML layer of the framework: the element tree, the renderer, static site generation and the WebAssembly build described below.
Philosophy and origins
The name Molt comes from ecdysis, the process by which an animal sheds its exoskeleton. Molting is a critical stage in the life of crustaceans โ a nod to Ferris, Rust's crab mascot. To grow, the organism sheds its old, restrictive shell and reveals a stronger, more adaptable one suited to the next stage.
For this project, the name marks a shift in architecture and mindset:
- Evolution from Swift. Molt grew out of deep experience with the Swift ecosystem, a language valued for its elegance and developer experience. Building scalable commercial SaaS products on minimal cloud infrastructure called for a pragmatic move, and Molt is that move to Rust and its backend efficiency.
- A production-ready foundation. The new exoskeleton is statically typed and memory-safe, a base meant for medium-to-large multi-tenant web applications and webhooks at scale.
Molt is about honoring our origins, shedding unnecessary overhead, and embracing maximum performance.
use *;
use Document;
let page = new
.head_children
.body_children;
println!;
Or with the html! macro:
use ;
let items = ;
let markup = html! ;
Why
Escaping you cannot forget. Text and attribute values are escaped when they enter the
tree, not when it is rendered โ so nothing is escaped twice and nothing is missed.
raw_text is the one, obvious, greppable escape hatch.
# use *;
assert_eq!;
No runtime, no DOM, no allocations you did not ask for. The renderer writes the whole tree into one buffer. There is no template parsing step and no reflection.
It runs in the browser. The same tree, the same renderer, compiled to
wasm32-unknown-unknown.
The tree is Send + Sync. Node is a value type, so bulk generation parallelises
safely โ that is what the parallel feature is built on.
Install
The crate is on crates.io and its API
documentation on docs.rs. It is published as
molt-framework โ molt on crates.io is an unrelated project โ and imported as molt.
[]
= "1.0"
use *;
Features:
| feature | default | what it does |
|---|---|---|
ssg |
โ | StaticSiteGenerator โ writes pages and assets to disk |
wasm |
wasm-bindgen bindings for JavaScript |
|
parallel |
rayon-backed bulk page generation |
What is in it
- 93 HTML elements, generated from one table โ see
docs/tag-catalog.md - Fluent builder API โ
set_id,add_class,add_classes,set_style,attr,data_attr(s),aria_attr(s),set_role,child,text,raw_text html!macro โ nesting, attributes,@if/@else,@forDocumentโ owns the doctype, renders pretty by defaultLayouttrait โ reusable page shells- SEO โ Open Graph, Twitter Cards, the common
<meta>block, and aSeoBuilder - Accessibility โ ARIA helpers plus an audit that finds images without
alt, buttons with no accessible name, iframes without a title - Sitemaps and RSS 2.0
- Static site generation โ atomic writes, asset copying, optional parallelism
- WebAssembly โ the same API from JavaScript
WebAssembly
import init from "./pkg/molt.js";
await ;
const page =
.
.;
document.. = page.;
The bindings wrap the real tree rather than accumulating pre-rendered strings, so
renderPretty() works and JavaScript drives exactly the renderer Rust does.
Handles are consumed. Builder methods and element arguments move their value into Rust,
so a handle you have already passed on (or chained from) is gone: using it throws
null pointer passed to rust. Chain calls, or keep the handle each call returns.
Known limitations
Pretty output is quadratic in nesting depth. Every line carries one indent string per
level above it, so a very deep tree renders a very large string. Rendering itself has no
depth limit โ the writer walks an explicit stack rather than recursing โ but the size is
worth knowing about when depth comes from untrusted input. See
SECURITY.md.
Documentation
GETTING_STARTED.md: a first page, escaping,html!, SEO, a site on disk.MIGRATION.md: from 0.1.0 to 1.0.0, change by change.SECURITY.md: what is guaranteed, the limits, how to report a problem.ROADMAP.mdandCHANGELOG.md.- API reference on docs.rs.
Development
verify.sh is a superset of CI. Run it before reporting that a change is done.
Contributing
Molt is open source and contributions are welcome โ a bug report with the input that triggered it is as useful as a patch.
- Found a bug or want a feature? Open an issue.
The
1.0.0milestone lists what is planned. - Found a security problem? Please do not open a public issue; use
a private security advisory.
See
SECURITY.md. - Sending code? Branch from
develop, add a test that asserts on the rendered markup, and run./scripts/verify.shbefore you open the pull request. The rules and the reasons behind them are inCONTRIBUTING.md. - Using an AI agent? Point it at
AGENTS.mdand the knowledge base in.claude/skills/molt: security rules, Rust practices and the lessons learned getting to 1.0.
Support the project
If Molt saves you time, you can help keep it maintained by sponsoring on GitHub. Starring the repository, sharing it, and telling me what you built with it help just as much.
License
MIT โ see LICENSE.
Created by Michel Anderson Lutz Teixeira.