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
[]
= "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
Rendering depth is bounded by the stack. The renderer recurses once per nesting level, so 256 levels is the guaranteed depth — deeper than browsers themselves render. Around 2,000 levels aborts the process, and a stack overflow is an abort rather than a catchable panic.
This is only reachable if nesting depth can be influenced by untrusted input. If it can,
bound the depth before building the tree or render on a thread with a larger stack. See
SECURITY.md and
#33.
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.