# rust_html
A HTML templating library for reusable components in web applications
## About
**rust_html** is a tiny templating library that let's you easily create
reusable HTML templates and components:
```rust
use rust_html::{rhtml, Template};
let card_component = |title: &str| {
rhtml! { r#"
{title}
"#}
};
let title = "My Website";
let my_template: Template = rhtml! {r#"
{title}
{card_component("Card A")}
{card_component("Card B")}
{card_component("Card C")}
"#};
let html_string: String = my_template.into();
println!("{}", html_string);
```
### Why use **rust_html**?
- Valid HTML syntax is enforced at compile-time
- Runtime rust values are automatically escaped to protect against injection attacks
- You can inject any expression or literal (not just identifiers)
The library is designed for creating reusable components for SSR
(server-side rendering), and is particularly nice in combination with front end libraries
like `alpine.js` or `htmx`. Unlike some other templating libraries, you can use the
standard HTML syntax directly and keep the templates next to your other Rust code.
## Installation
Install from [crates.io](https://crates.io/crates/rust_html) using cargo:
```bash
cargo add rust_html
```
## Usage
### Types
The library has only 5 exported functions/types:
- `rhtml!`: The main macro for creating templates
- `Template`: represents a reusable HTML template
- `Render`: trait for implementing reusable `struct` components
- `Unescaped`: string wrapper for inserting unescaped values
- `TemplateGroup`: wrapper to insert a `Vec`
> [!NOTE]
> The `Template` struct itself does not implement the `Display` trait.
> To print or return the HTML value as a `String` you can use `String::from(my_template)`
> or just `my_template.into()` where applicable.
### The `rhtml!` macro
The `rhtml!` macro accepts a single string literal as input, typically
`"my text here"` or `r#"my text here"#` which is a bit more convenient for HTML.
The macro returns a `Template` struct that ensures injection safety when
reusing template within templates.
Inside the macro string you can inject anything that implements either the
`std::fmt::Display` or `Render` trait by using brackets `{}`.
You can escape brackets inside the HTML by using two of them in a row (`{{` or `}}`).
### Example - Reusable Components
```rust
use rust_html::{rhtml, Template, TemplateGroup};
/// Reusable card component with a title property
fn card_component(title: &str) -> Template {
rhtml! { r#"{title}
"# }
}
/// Reusable card group component that creates N cards
fn card_row_component(n_cards: u32, container_class: &str) -> Template {
// For injecting lists of templates, we can use a TemplateGroup
let cards: TemplateGroup = (0..n_cards)
.map(|card_index| {
let title = format!("Card {}", card_index);
card_component(&title)
})
.collect();
rhtml! { r#"
{cards}
"# }
}
// Server endpoint
fn your_endpoint() -> String {
let page_template: Template = rhtml! { r#"
{card_row_component(3, "my_card_row")}
"# };
// Convert the `Template` to `String`
// This is typically only done in the endpoint just before
// returning the full HTML. Make sure you also return a
// `Content-Type` of `text/html` in your response
page_template.into()
}
```
The `your_endpoint` function will return the following HTML:
```html
```
### Expressions inside a template
In some cases you might want to include simple logic in your
template directly. You can use any valid Rust expression inside the macro:
```rust
use rust_html::rhtml;
fn main() {
let age = 32;
let page = rhtml! { r#"
Bob is {if age >= 18 { "an adult" } else { "not an adult" }}
"# };
println!("{}", String::from(page));
// Output is 'Bob is an adult
'
}
```
To prevent ambiguity you must only use `{` and `}` for opening/closing scopes
in the injected rust code - not inside literals or comments.
The following is therefore not valid:
```rust
rhtml! { r#"{ if true { "}" } else { "" } }"# };
// ^
// Bracket not allowed
```
### Structs as reusable components
You can also use structs as components by implementing the `Render` trait.
```rust
use rust_html::{rhtml, Render, Template};
#[derive(Clone)]
struct CardComponent {
title: String,
content: String,
}
// Implement rust_html rendering for our component
impl Render for CardComponent {
fn render(&self) -> Template {
rhtml! {r#"
{self.title}
{self.content}
"#}
}
}
fn main() {
let my_card = CardComponent {
title: "Welcome".to_string(),
content: "This is a card".to_string(),
};
let page = rhtml! {r#"
{my_card}
"#};
}
```
## Escaping
Template input is escaped by default to prevent injection attacks, for instance if
a user were to register with a name that contains a `";
let page = rhtml! {r#"{sketchy_user_input}
"#};
println!("{}", String::from(page));
```
Generates a string where dangerous characters are escaped:
```html
<script>alert('hi')</script>
```
### Unescaping
If you need the unescaped value, you can use the `Unescaped` wrapper.
> [!CAUTION]
> Never use `Unescaped` on untrusted user input or if you don't
> know what you're doing.
```rust
use rust_html::{rhtml, Unescaped};
let sketchy_user_input = "";
let unescaped = Unescaped(sketchy_user_input.to_string());
let page = rhtml! {r#"{unescaped}
"#};
println!("{}", String::from(page));
```
...which results in this string:
```html
```
## Integration with web frameworks
Integrating with any web framework is trivial - simply convert the
template string to the response type for the given framework.
If you're using Axum you can add the `axum` feature to get support
for their `IntoResponse` trait.
## Related projects
- [maud](https://github.com/lambda-fairy/maud): rust syntax for HTML
- [askama](https://github.com/djc/askama): jinja like templating library
- [tera](https://github.com/Keats/tera): jinja2 like templating library
- [handlebars-rust](https://github.com/sunng87/handlebars-rust): handlebars templating language for rust
Look at the [AWWY](https://www.arewewebyet.org/topics/templating/) website for more examples.
## Contributing
Run tests:
```bash
cargo test -p rust_html_tests
```
Run doc tests:
```bash
cargo test -p rust_html_macros
```