RTML - 在 Rust 中描述 HTML
rtml
在 Rust 中嘗試描述 html/css
在通常情況下要在 Rust 中生成 html 除了手動拼字符串外最常用的方式是使用模板引擎, 但這會帶來 2 個主要問題
-
1. 需要學習模板語法, 且不同模板引擎之間語法可能差異很大
-
2. 在模板中難以利用 Rust 類型系統, 以及在 IDE 中支持可能不佳
那有沒有可能在 Rust 實現 html 那樣可以比較方便地展示文檔樹形結構, 同時又能充分利用 Rust 的類型系統呢, rtml 正是這個想法的一次嘗試.
使用
先看一個簡單例子
use rtml::tags::*;
fn main() {
let page = html(
h1("hello world!")
);
println!("{}", page);
}
生成 html
<html>
<h1>
hello world!
</h1>
</html>
可以看到在 rtml 中, html, h1 和 html 的 <html>
, <h1>
標籤對應, 往 html
傳入的參數是 <html> </html>
的 children, html(h1(...))
就如同往 <html>
標籤中間放入 h1
標籤一樣.
注意, 在 rtml 中, h1
, div
, 和 title
等都是普通的函數 (以下簡稱標籤函數), 沒有使用任何過程宏, 對於有 LSP 支持的 IDE 來說, 懸浮顯示, 智能補全, 跳轉定義等都沒有問題. 而且 rtml 對於標準 html 標籤都提供了詳細的文檔, 方便使用.
靈活的 children 傳入
對於 rtml 中任意一個標籤函數, 其參數雖然只有一個, 但可傳入其中, 作爲 children 的類型非常多.
常見的字面類型
// 布爾值
span(true)
// 浮點數
span(1.1f64)
// 字符串
span("hello")
// format! 也OK
let name = "Rookie";
span(format!("hello {name}"))
// 整數
span(666usize)
更多類型
// 任意一個實現 Tag trait 的結構體
div(h1("hello"))
// 元組也OK
// div 傳入的參數是一個 (H1, Hr, P) 元組
div((
h1("hello"),
hr(()),
p("this is some desc")
))
// Vec 也行
// ul 傳入的是一個
ul(
[1, 2, 3]
.iter()
.map(|i| li(format!("item {i}")))
.collect::<Vec<_>>()
)
// const generic array 也可以, 如傳入 [Span; 2]
div([span(1), span(2)])
設置屬性, 樣式和綁定事件處理函數
在 html 標籤上, 用戶可以設置 id, class 樣式等. 爲了兼容 html 風格, 樣式設置格式, rtml 提供了 style!
, 和 prop!
兩個宏以方便屬性設置. 而且 rtml 傳入屬性的方式也非常靈活.
如果查看 rtml 文檔裏某個標籤函數參數結構體 xxArgs 的文檔, 你會發現它實現很多 From<xxx>
方法.
正是這些實現讓 rtml 的構造函數很靈活.
對於 <meta>
等標籤, 通常我們不關係他的 children, 而是要傳入 charset 等屬性, 這時候你可以這樣傳參.
meta(prop! {
charset = "utf-8"
})
對於某些標籤來說, 屬性, 樣式和 children 都需要設置, 除了要求 children 必須在最後一個位置外, 屬性和樣式可以亂序
// 注意, 傳入的一個三元素元組
div((
style!{color: "red"},
prop! { id = "app" },
p("hello")
))
下面是一個更復雜的例子
use rtml::prop;
use rtml::style;
use rtml::tags::*;
fn main() {
let page = html((
prop! { lang = "zh-cn" },
(
header((
meta(prop! { charset = "utf-8" }),
title("html file generated by Rust!"),
)),
body((
style! {
color: "olive";
text-align: "center"
},
(
h1("WOW"),
hr(()),
h2("循環"),
pre((
style! {},
code(
r#"[1, 2, 3].iter().map(|i| p(format!("paragraph {}", i))).collect::<Vec<_>>()"#,
),
)),
div([1, 2, 3]
.iter()
.map(|i| p(format!("paragraph {}", i)))
.collect::<Vec<_>>()),
hr(()),
h2("任意字面量"),
div((
style! { color: "black" },
(span(true), span(false), span(1u8), span(1.30f32)),
)),
hr(()),
footer(b("power by Rust!")),
),
)),
),
));
println!("{}", page);
}
生成的 html
<html lang=zh-cn>
<header>
<meta charset=utf-8>
</meta>
<title>
html file generated by Rust!
</title>
</header>
<body style="color: olive; text-align: center; ">
<h1>
WOW
</h1>
<hr>
</hr>
<h2>
循環
</h2>
<pre>
<code>
[1, 2, 3].iter().map(|i| p(format!("paragraph {}", i))).collect::<Vec<_>>()
</code>
</pre>
<div>
<p>
paragraph 1
</p>
<p>
paragraph 2
</p>
<p>
paragraph 3
</p>
</div>
<hr>
</hr>
<h2>
任意字面量
</h2>
<div style="color: black; ">
<span>
true
</span>
<span>
false
</span>
<span>
1
</span>
<span>
1.3
</span>
</div>
<hr>
</hr>
<footer>
<b>
power by Rust!
</b>
</footer>
</body>
</html>
瀏覽器打開效果如下.
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/PegJF-cJ0ZRiZJti4O27Ew