Crate hypertext

source ·
Expand description

A blazing fast type-checked HTML macro crate.

Features

Speed

The macros generate code that is as fast as writing HTML to a string by hand, and intelligently combines what would be multiple push_str calls into one if there is no dynamic content between them.

The entire crate is #![no_std] compatible, and allocation is completely optional if you don’t use any dynamic content. Disabling the alloc feature and using maud_static!/rsx_static! will result in an Rendered<&str>, which can even be used in const contexts!

The crate gives extreme importance to lazy rendering and minimizing allocation, so it will only render the HTML to a string when you finally call Renderable::render at the end. This makes composing nested HTML elements extremely cheap.

Type-Checking

All macros are validated at compile time, so you can’t ever misspell an element/attribute or use invalid attributes.

It does this by looking for a module in your current namespace named html_elements (all the valid HTML elements are defined in this crate already in html_elements, but it doesn’t hard-code this module so you can define your own elements).

It then imports each element you use in your macro invocation as a struct, and then proceeds to attempt to access the corresponding associated type for each attribute you use.

Examples

use hypertext::{html_elements, maud, GlobalAttributes, Renderable};

maud! {
    div #main title="Main Div" {
        h1.important {
            "Hello, world!"
        }
    }
}
.render()

// expands to (roughly):

{
    const _: () = {
        html_elements::div;
        html_elements::h1;
        let _: hypertext::Attribute = html_elements::div::id;
        let _: hypertext::Attribute = html_elements::div::title;
        let _: hypertext::Attribute = html_elements::h1::class;
    };

    |hypertext_output: &mut String| {
        hypertext_output.push_str(
            r#"<div id="main" title="Main Div"><h1 class="important">Hello, world!</h1></div>"#
        );
    }
}
.render()

This approach is also extremely extensible, as you can define your own traits to add attributes for your favourite libraries! In fact, this is exactly what GlobalAttributes does, and why it is required in the above example. GlobalAttributes defines all the global attributes that can be used on any element, for example id, class and title.

Here’s an example of how you could define your own attributes for use with the wonderful frontend library htmx:

use hypertext::{html_elements, maud, Attribute, GlobalAttributes, Renderable};

trait HtmxAttributes: GlobalAttributes {
    const hx_get: Attribute = Attribute;
    const hx_post: Attribute = Attribute;
    // ...
}

impl<T: GlobalAttributes> HtmxAttributes for T {}

assert_eq!(
    //          vvvvvv note that it converts `-` to `_` for you during checking!
    maud! { div hx-get="/api/endpoint" { "Hello, world!" } }.render(),
    r#"<div hx-get="/api/endpoint">Hello, world!</div>"#,
);

Modules

Macros

Structs

Traits