6 releases (3 breaking)

new 0.4.1 May 23, 2024
0.4.0 May 23, 2024
0.3.0 May 21, 2024
0.2.1 May 14, 2024
0.1.0 May 8, 2024

#17 in #html-string

Download history 87/week @ 2024-05-02 319/week @ 2024-05-09 118/week @ 2024-05-16

524 downloads per month
Used in fhtml

MIT license

15KB
291 lines

fhtml

Simple and efficient macros for writing HTML in Rust

Overview

fhtml Provides formatting macros for writing HTML without the annoyance of dealing with HTML inside string literals. A few highlights:

  • simplicity: no complex templating syntax, just plain HTML with embedded expressions and format specifiers
  • zero extra allocations: fhtml macros expand to their std counterpart with no indirections or added allocations
  • compatibility: since fhtml is simply a wrapper over std macros, meaning that you can easily use idiomatic Rust, such as implementing fmt::Display or fmt::Write for creating components, or integrate with existing libraries and tools
  • safety: fhtml provides an easy way to escape values (escaping is NOT done implicitly)

Installation

In your Cargo.toml:

[dependencies]
fhtml = "0.4"

Syntax

  • HTML is typed as-is, unquoted:
fhtml::format!(<input />);
  • Expressions are passed in using braces:
fhtml::format!(<div>{1 + 1}</div>);
  • Text nodes are quoted:
fhtml::format!(<p>"Some text"</p>);
  • Format specifiers are written after expressions:
fhtml::format!(<code>{vec![1, 2, 3]:?}</code>);
  • Escaping is done by using an exclamation mark ! as a format specifier:
fhtml::format!(<div>{"<b>Dangerous input</b>":!}</div>);

this being the only format specifier deviating from the std::fmt syntax

Usage

Writing to a buffer

let mut buffer = String::new();
fhtml::write!(buffer, <div>"Hello, World!"</div>);

Escaping

let user_input = "<b>Dangerous input</b>";
fhtml::format!(<div>{user_input:!}</div>); // "<div>&lt;b&gt;Dangerous input&lt;/b&gt;</div>"

Components

Since fhtml macros expands to their std counterpart, you are free to create components however you prefer

Function components

fn heading(label: &'static str) -> String {
    fhtml::format! {
        <h1>{label}</h1>
    }    
}

let page = fhtml::format! {
    <main>
        {heading("My Heading")}
        <div>"My Content"</div>
    </main>
};

Struct components

use std::fmt;

struct Product {
    name: &'static str,
    price: f32,
}

impl fmt::Display for Product {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fhtml::write! { f,
            <article>
                <h2>{self.name}</h2>
                <h3>"$" {self.price}</h3>
            </article>
        }
    }
}

let products = fhtml::format! {
    <h1>"Our products"</h1>
    {Product {
      name: "Arabica Coffee Beans",
      price: 3.99
    }}
    {Product {
      name: "Sourdough Bread",
      price: 2.49
    }}
};

Format specifiers

fhtml::format!(<code>{vec![1, 2, 3]:?}</code>); // "<code>[1, 2, 3]</code>"
fhtml::format!(<span>{10:#b}</span>);           // "<span>0b1010</span>"

Iterators

fhtml::format! {
    <ul>
        {
            (0..10).fold(String::new(), |mut f, i| {
                let _ = fhtml::write! { f,
                    <li>{i}</li>
                };
                f
            })
        }
    </ul>
}

Dependencies

~305–760KB
~18K SLoC