winged-rust
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.
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. 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, its API documentation on docs.rs, and the WebAssembly build on npm.
[]
= "1.0"
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/winged_rust.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.
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 WINGED_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
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.