hypertext Rust Crate

repository·main·Indexed 42 days ago

https://github.com/vidhanio/hypertext

A high-performance, type-checked HTML macro crate for Rust providing compile-time validation for elements and attributes. It features two primary macros, `maud!` for attribute-based syntax and `rsx!` for JSX-like syntax, and supports standard HTML, SVG, MathML, and extensible frameworks like htmx and Alpine.js. Designed for speed with lazy rendering and `#![no_std]` compatibility.

Tokens
8.5K
Snippets
23
Records
32
Agent score
55%

What's inside hypertext

  1. Overview of hypertext

    main
    hypertext is a high-performance, type-checked HTML macro crate for Rust. It provides compile-time validation for element names and attributes, supporting both standard HTML and extensible custom frameworks like htmx and Alpine.js. It is designed for speed using lazy rendering to minimize allocations and supports #![no_std] environments.
  2. Core features of hypertext

    main

    Speed

    • Generates code as fast as manual push_str calls.
    • Intelligently combines multiple push_str calls into one when no dynamic content exists between them.
    • #![no_std] compatible; allocation is optional via maud::simple! and rsx::simple! if no dynamic content is used.
    • Employs lazy rendering to minimize allocation.

    Type-Checking

    • All macro invocations are validated at compile time.
    • Prevents misspelling elements/attributes, using non-existent attributes, or using attributes on unsupported elements.
    • Distinguishes between void elements and normal elements at compile time.
  3. Element Kinds: Normal, Void, and Xml

    main

    Hypertext categorizes elements into three distinct kinds to enforce correct markup structure:

    • Normal: Elements that must have a closing tag and are allowed to have children (e.g., <div>).
    • Void: Elements that cannot have children and cannot have a closing tag (e.g., <img>).
    • Xml: Elements used in XML-based languages (like SVG). These can either have a closing tag with children OR be self-closing (e.g., <circle /> or <g>...</g>).
  4. How compile-time element and attribute validation works

    main

    Hypertext uses Rust's type system to validate HTML/SVG/MathML elements and attributes at compile time.

    Element Validation

    Elements are validated by searching for a unit struct with the same name that implements the Element trait. The library searches your current scope first, then falls back to a module named hypertext_elements. This module is typically available via use hypertext::prelude::*;.

    Attribute Validation

    Attributes are validated by accessing a constant on the element type that matches the attribute name.

    Bypassing Validation

    If you want to skip type-checking for an attribute (e.g., if it cannot be parsed or you don't care about validation), you can:

    1. Wrap the attribute name in double quotes (e.g., "my-unvalidated-attr"="value").
    2. Use data-* attributes, which are never validated.

    To define your own validated elements, use the provided define_*_elements! macros within the appropriate module (see below).

  5. Understand the Context trait and rendering targets

    main

    The Context trait is a marker trait used to distinguish between different rendering targets in hypertext. This ensures type safety by preventing the accidental use of node-specific logic in attribute contexts, and vice versa.

    There are two primary types of contexts:

    1. Node<K>: Represents a complete markup node (like an element) within a specific markup language K. By default, Node uses Html as its kind.
    2. AttributeValue: Represents a value intended for an attribute. These values are expected to be eventually wrapped in double quotes.

    Escaping Requirements:

    • When rendering to a Node, you must escape & to &amp;, < to &lt;, and > to &gt;.
    • When rendering to an AttributeValue, you must escape & to &amp;, < to &lt;, > to &gt;, and " to &quot;.
  6. Handle rendered output with Rendered<T, K>

    main

    The Rendered<T, K> type represents a string that has already been processed into HTML. This type is returned by .render() and Raw<T, K>::rendered().

    Note: Rendered does not implement Renderable. To avoid the anti-pattern of rendering to a string and then embedding that string into another page, use RenderableExt::memoize or Raw instead.

  7. Use NodeKind to specify markup languages

    main

    The NodeKind trait is used to parameterize Node<K> to specify which markup language is being constructed. The following marker types are available:

    • Html: The default kind for HTML nodes.
    • Svg: Used for SVG nodes.
    • MathMl: Used for MathML nodes.

    These are used to create specialized type aliases for convenience.

  8. Use standard HTML attributes with GlobalAttributes

    main

    The GlobalAttributes trait provides access to standard HTML attributes like class, id, style, and tabindex. This trait is implemented for all standard HTML elements. To use these attributes in your templates (e.g., with maud!), ensure the trait is in scope, typically by importing hypertext::prelude::*.

    use hypertext::prelude::*;
    
    // Standard attributes are available on all elements
    // e.g., class, id, style, title, etc.
  9. Use the Buffer type for HTML rendering

    main

    The Buffer<C> type is a wrapper around String used for efficient and safe HTML rendering. It uses a phantom type C (implementing the Context trait) to enforce different escaping rules depending on the intended output context (e.g., standard HTML nodes, attribute values, SVG, or MathML). This prevents accidental XSS vulnerabilities by requiring explicit opt-ins when using raw strings.

    use hypertext::Buffer;
    use hypertext::context::Node;
    
    let mut buffer = Buffer::<Node>::new();
    // ... use buffer to render content
  10. Define custom attributes for a library

    main

    If a frontend library is not supported by hypertext, you can define your own attribute trait by implementing it for any type that satisfies GlobalAttributes. Note that when using these in macros like maud!, the validator automatically converts hyphens (-) to underscores (_).

    use hypertext::{prelude::*, validation::Attribute};
    
    trait MyLibraryAttributes: GlobalAttributes {
        const my_attr: Attribute = Attribute;
        const my_other_attr: Attribute = Attribute;
    }
    
    impl<T: GlobalAttributes> MyLibraryAttributes for T {}
    
    // Usage in template:
    // maud! { div my-attr="value" my-other-attr="other value" { "Hello" } }
  11. Define custom HTML elements with `define_elements!`

    main

    To define custom HTML elements with compile-time validation, use the define_elements! macro.

    Important: This macro must be called from within a module named hypertext_elements.

    Example:

    mod hypertext_elements {
        use hypertext::define_elements;
        // Re-export all standard HTML elements to ensure they are available
        pub use hypertext::validation::hypertext_elements::*;
    
        define_elements! {
            /// A custom web component
            simple_greeting {
                /// The name of the person to greet
                name
            }
        }
    }
    
    // Usage:
    use hypertext::prelude::*;
    // maud! will now recognize `simple-greeting` and validate the `name` attribute
    mod hypertext_elements {
        use hypertext::define_elements;
        // Re-export all standard HTML elements
        pub use hypertext::validation::hypertext_elements::*;
    
        define_elements! {
            /// A custom web component that greets the user.
            simple_greeting {
                /// The name of the person to greet.
                name
            }
    
            /// An element representing a coordinate.
            coordinate {
                /// The x coordinate.
                x
    
                /// The y coordinate.
                y
            }
        }
    }
    
    // Now, you can use the custom elements like this:
    
    use hypertext::prelude::*;
    
    assert_eq!(
        maud! {
            simple-greeting name="Alice" {
                coordinate x=1 y=2 {}
            }
        }
        .render()
        .as_inner(),
        r#"<simple-greeting name="Alice"><coordinate x="1" y="2"></coordinate></simple-greeting>"#,
    )
  12. Define custom MathML elements with `define_mathml_elements!`

    main

    To define custom MathML elements, use the define_mathml_elements! macro.

    Important: This macro must be called from within a module named hypertext_mathml_elements.

    Example:

    mod hypertext_mathml_elements {
        use hypertext::define_mathml_elements;
        pub use hypertext::validation::hypertext_mathml_elements::*;
    
        define_mathml_elements! {
            custom_op {
                my_attr
            }
        }
    }
    mod hypertext_mathml_elements {
        use hypertext::define_mathml_elements;
        // Re-export all standard MathML elements
        pub use hypertext::validation::hypertext_mathml_elements::*;
    
        define_mathml_elements! {
            /// A custom MathML element.
            custom_op {
                /// A custom attribute.
                my_attr
            }
        }
    }
    
    // Now, you can use the custom elements like this:
    
    use hypertext::prelude::*;
    
    assert_eq!(
        mathml::maud! {
            custom-op my-attr="value";
        }
        .render()
        .as_inner(),
        r#"<custom-op my-attr="value"/>"#,
    )