molt-framework 0.1.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

Fast, type-safe HTML generation for Rust — and for the browser, through WebAssembly.

A port of Winged-Swift 2.0.0: the same composite tree, the same escape-by-default guarantee, the same compact and pretty render modes. Verified against the Swift project's own golden fixtures, byte for byte.

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.

Brand

The logo is a continuous-line geometric mark that combines a lightning bolt (speed), wings (lightness, and the Winged-Swift lineage), a gear (Rust engineering) and crab claws (Ferris). The primary colour is deep navy/slate (#1A2B3C) on white. The typeface is Montserrat: Semi-Bold or Bold for the name, Light or Regular with +12% tracking for the tagline.

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. Winged-Swift's HTMLTag is a mutable class, which is why a Swift tag tree is not Sendable and why its ROADMAP.md lists that as an unresolved 3.0 problem. 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 = "0.1"
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.

Parity with Winged-Swift

The Swift project ships four golden fixtures. This crate reproduces all four byte for byte, which is what turns "a port" into a proven port:

fixture what it exercises
marketing-pretty.html doctype, lang, 17 meta tags, tables, boolean attributes, forms, <pre><code>, entity escaping
marketing-compact.html the same tree, minified
sitemap.xml XML escaping, float formatting
feed.xml RSS 2.0 with atom:self

Run MOLT_UPDATE_FIXTURES=1 cargo test --test golden to regenerate them. A regenerated fixture in a pull-request diff is the signal that markup changed.

Deliberate differences from the Swift API — and the reason for each — are listed in PORTING.md.

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.

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

See CONTRIBUTING.md, and AGENTS.md if you are an agent.

License

MIT — see LICENSE.

Original Swift implementation by Michel Anderson Lutz Teixeira.