6 stable releases
1.1.4 | Dec 3, 2024 |
---|---|
1.1.2 | Dec 2, 2024 |
1.1.1 | Dec 1, 2024 |
1.0.0 | Dec 1, 2024 |
#671 in Template engine
520 downloads per month
Used in rust_html
28KB
483 lines
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:
use rust_html::{rhtml, Template};
let card_component = |title: &str| {
rhtml! { r#"
<div class="card">
{title}
</div>
"#}
};
let title = "My Website";
let my_template: Template = rhtml! {r#"
<div class="page">
<h1>{title}</h1>
{card_component("Card A")}
{card_component("Card B")}
{card_component("Card C")}
</div>
"#};
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 using cargo:
cargo add rust_html
Usage
Types
The library has only 5 exported functions/types:
rhtml!
: The main macro for creating templatesTemplate
: represents a reusable HTML templateRender
: trait for implementing reusablestruct
componentsUnescaped
: string wrapper for inserting unescaped valuesTemplateGroup
: wrapper to insert aVec<Template>
[!NOTE]
TheTemplate
struct itself does not implement theDisplay
trait. To print or return the HTML value as aString
you can useString::from(my_template)
or justmy_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
use rust_html::{rhtml, Template, TemplateGroup};
/// Reusable card component with a title property
fn card_component(title: &str) -> Template {
rhtml! { r#"<div class="card">{title}</div>"# }
}
/// 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#"
<div class="{container_class}">
{cards}
</div>
"# }
}
// Server endpoint
fn your_endpoint() -> String {
let page_template: Template = rhtml! { r#"
<div class="page">
{card_row_component(3, "my_card_row")}
</div>
"# };
// 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:
<div class="page">
<div class="my_card_row">
<div class="card">Card 0</div>
<div class="card">Card 1</div>
<div class="card">Card 2</div>
</div>
</div>
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:
use rust_html::rhtml;
fn main() {
let age = 32;
let page = rhtml! { r#"
<div>
Bob is {if age >= 18 { "an adult" } else { "not an adult" }}
</div>
"# };
println!("{}", String::from(page));
// Output is '<div>Bob is an adult</div>'
}
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:
rhtml! { r#"{ if true { "}" } else { "" } }"# };
// ^
// Bracket not allowed
Structs as reusable components
You can also use structs as components by implementing the Render
trait.
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#"
<div class="card">
<h1>{self.title}</h1>
<p>{self.content}</p>
</div>
"#}
}
}
fn main() {
let my_card = CardComponent {
title: "Welcome".to_string(),
content: "This is a card".to_string(),
};
let page = rhtml! {r#"
<div class="page">
{my_card}
</div>
"#};
}
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 <script>
tag.
The following snippet:
let sketchy_user_input = "<script>alert('hi')</script>";
let page = rhtml! {r#"<div>{sketchy_user_input}</div>"#};
println!("{}", String::from(page));
Generates a string where dangerous characters are escaped:
<div><script>alert('hi')</script></div>
Unescaping
If you need the unescaped value, you can use the Unescaped
wrapper.
[!CAUTION]
Never useUnescaped
on untrusted user input or if you don't know what you're doing.
use rust_html::{rhtml, Unescaped};
let sketchy_user_input = "<script>alert('hi')</script>";
let unescaped = Unescaped(sketchy_user_input.to_string());
let page = rhtml! {r#"<div>{unescaped}</div>"#};
println!("{}", String::from(page));
...which results in this string:
<div>
<script>
alert("hi");
</script>
</div>
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: rust syntax for HTML
- askama: jinja like templating library
- tera: jinja2 like templating library
- handlebars-rust: handlebars templating language for rust
Look at the AWWY website for more examples.
Contributing
Run tests:
cargo test -p rust_html_tests
Run doc tests:
cargo test -p rust_html_macros
Dependencies
~3.5–8.5MB
~83K SLoC