Traits
Component, Render, Renderer, their async counterparts, and what the derive implements.
Component
pub trait Component: Render {
fn default_renderer(&self) -> Box<dyn Renderer>;
fn render(&self) -> String { … }
fn render_with(&self, slots: Slots<'_>) -> String { … }
}Generated by the derive, which implements default_renderer — a boxed
HtmlRenderer — and takes the other two from the trait. render is
render_with(Slots::EMPTY); render_with builds the default renderer, calls
render_slots, and returns finish(). Object-safe, so &dyn Component and
Vec<Box<dyn Component>> work.
render_with is the Rust-side equivalent of <Comp>…</Comp> in a template — see
Slots.
Render
pub trait Render {
fn render_into(&self, r: &mut dyn Renderer);
fn render_slots(&self, r: &mut dyn Renderer, slots: Slots<'_>) { … }
}The lower-level half: write yourself into a renderer someone else owns. The
derive implements render_slots with the lowered template and points
render_into at it with Slots::EMPTY.
The default render_slots forwards to render_into, ignoring its slots. That
suits content with no <slot>s of its own — a Fragment, or a hand-written impl
— and lets such an impl stay a single method.
Blanket impls, all forwarding both methods:
| Impl | Behaviour |
|---|---|
Render for &T where T: Render + ?Sized |
renders what it points at — which is what makes the &dyn Render a Slots hands back renderable |
Render for Box<T> where T: Render + ?Sized |
Box<dyn Render> for heterogeneous children |
Render for Option<T> where T: Render |
None renders nothing, so {@render slots.get("x")} needs no guard |
Render is object-safe too.
AsyncComponent and AsyncRender
pub type RenderFuture<'a> = Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
pub trait AsyncRender: Sync {
fn render_into_async<'a>(&'a self, r: &'a mut dyn Renderer) -> RenderFuture<'a>;
fn render_slots_async<'a>(&'a self, r: &'a mut dyn Renderer, slots: Slots<'a>)
-> RenderFuture<'a> { … }
}
pub trait AsyncComponent: AsyncRender {
fn default_renderer(&self) -> Box<dyn Renderer>;
fn render_async(&self) -> Pin<Box<dyn Future<Output = String> + Send + '_>> { … }
fn render_with_async(&self, slots: Slots<'_>)
-> Pin<Box<dyn Future<Output = String> + Send + '_>> { … }
}The same two traits with values replaced by boxed futures, implemented by the
derive instead of Component/Render for a template that contains .await
— see Async templates. Boxed because an async fn in a trait is
not dyn-safe and these stay object-safe like the rest; that allocation is why
the derive only reaches for them when a template actually awaits.
Blanket impls run one way only:
| Impl | Effect |
|---|---|
AsyncRender for T where T: Render + Sync + ?Sized |
every sync component is async-renderable — its future has nothing to poll |
AsyncComponent for T where T: Component + Sync + ?Sized |
render_async / render_with_async work on a sync component too |
There is no impl in the other direction: rendering an async component synchronously would mean blocking on a future inside the caller’s own executor.
RenderFuture is Send so a server executor can drive a render. That is what
Renderer: Send, AsyncRender: Sync and a slot fill’s Sync are for — one
bound per thing the future holds across an await.
Renderer
The output buffer and the escaping policy, in one object-safe trait. Components
are compiled against &mut dyn Renderer, so a component compiled once is driven
by any renderer.
Renderer: Send, because an async render holds &mut dyn Renderer across its
awaits and a future holding one is Send only if the renderer is. A buffer is
Send unless it was built from something deliberately not.
| Method | Backs | Default |
|---|---|---|
write_raw(&str) |
tags and already-safe content | required |
write_escaped(&dyn Display) |
{ … } |
required |
finish(self: Box<Self>) -> String |
producing the output | required |
write_text(&str) |
literal text between tags | forwards to write_raw |
write_display_raw(&dyn Display) |
{@html … } |
formats through write_raw |
push_indent(usize) / pop_indent(usize) |
layout | no-op |
close_line(usize) |
layout | no-op |
set_verbatim(bool) |
<pre>, <textarea>, <script>, <style> |
no-op |
Everything with a default exists so that a renderer written before those hooks did stays correct without being touched — and so a minimal renderer is three methods.
write_text is separate from write_raw because it is the only markup a
renderer may lay out: it is the whitespace between elements, which HTML renders
as a single space however much of it there is. A tag’s own bytes are never
re-laid-out, since the only newline one can contain is inside an attribute value,
and re-indenting a multi-line title would change what it says.
write_display_raw defaults to formatting the value into a String and
passing it to write_raw. A buffer-backed renderer should override it to write
in place instead.
push_indent / pop_indent exist because indentation is a property of the
call site, not of the component: one compiled render_into serves every place
a component is used, and those sit at different depths, so the depth cannot be
baked into a component’s literals. The caller — which knows its own depth
statically — opens the levels, and the renderer carries the running total.
close_line(depth) sets the indentation already written for the current line
to depth levels below that running total, because what comes next is the end
tag of an element at that depth. Only the run-time side can get this right: the
last thing an element writes before its end tag may come from a {#if} that
rendered nothing, leaving a separator written for a child — a level too deep —
and which branch ran is not known until it runs. It does nothing where the line
is not open, so <span>Wi-Fi</span> gains no break.
set_verbatim(bool) enters and leaves a region where whitespace is
significant. It nests, because such an element can contain a component containing
more of them.
Why a child renders through its parent’s renderer
{@render} and component elements write into the same renderer as their
parent. Escaping is therefore a property of the output, not of the component: a
component cannot escape differently depending on who called it.
Prelude
use damask::prelude::*;Brings in Component (trait and derive), Render, Renderer, Slot, Slots,
DEFAULT_SLOT, fragment, HtmlRenderer, StringRenderer, Whitespace, the
async set AsyncComponent, AsyncRender, RenderFuture and fragment_async,
and the attribute traits Attr, AttrSpread, AttrSet, Attrs,
IntoAttrValue, TokenItem, TokenList.
The attribute traits are documented where the syntax they back is: Attr and
AttrSpread in Attributes, TokenItem and TokenList in
Token lists, AttrSet, IntoAttrValue and Attrs in
Attribute groups.