molt-framework 1.0.0

Fast, type-safe HTML DSL and static site generation engine in Rust and WebAssembly
Documentation

Molt

High-Performance Rust Web Framework

crates.io docs.rs CI License: MIT Rust 1.85+ WebAssembly Sponsor

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:

  1. 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.
  2. 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 molt::prelude::*;
use molt::Document;

let page = Document::new(Some("pt-BR"))
    .head_children([
        meta().attr("charset", "UTF-8"),
        title().text("RideKeeper โ€” track every service"),
        stylesheet("/css/style.css"),
    ])
    .body_children([
        header().child(nav().child(link_to("/").add_class("logo").text("RideKeeper"))),
        main_tag().child(
            article()
                .add_class("card p-4")
                .child(h1().text("Track every service"))
                .child(p().text("Fuel, tyres & chain โ€” all in one place.")),
        ),
    ]);

println!("{}", page.render());

Or with the html! macro:

use molt::{html, prelude::*};

let items = ["Fuel", "Tyres", "Chain"];
let markup = html! {
    section(class = "log") {
        h2 { "Service log" }
        ul { @for item in items { li { (item) } } }
    }
};

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 molt::prelude::*;
assert_eq!(p().text("<script>alert(1)</script>").render(),
           "<p>&lt;script&gt;alert(1)&lt;/script&gt;</p>");

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.

[dependencies]
molt-framework = "1.0"
cargo add molt-framework
use molt::prelude::*;

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, @for
  • Document โ€” owns the doctype, renders pretty by default
  • Layout trait โ€” reusable page shells
  • SEO โ€” Open Graph, Twitter Cards, the common <meta> block, and a SeoBuilder
  • 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

wasm-pack build --target web --features wasm
import init, { element, WDocument } from "./pkg/molt.js";
await init();

const page = new WDocument("pt-BR")
  .addBody(element("h1").text("Track every service"))
  .addBody(element("p").addClass("lead").text("Fuel, tyres & chain."));

document.body.innerHTML = page.render();

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

Development

./scripts/verify.sh        # build, test, clippy, fmt, catalog, fixtures, smoke test
cargo test                 # unit, integration and doc tests
cargo bench                # render performance

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.0 milestone 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.sh before you open the pull request. The rules and the reasons behind them are in CONTRIBUTING.md.
  • Using an AI agent? Point it at AGENTS.md and 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.