5 个不稳定版本
0.3.1 | 2020年6月1日 |
---|---|
0.3.0 | 2019年11月27日 |
0.2.0 | 2019年9月23日 |
0.1.1 | 2019年9月21日 |
0.1.0 | 2019年9月21日 |
#26 在 #ergonomics
每月 49 次下载
在 2 个 crate 中使用(通过 render)
23KB
500 行
render
🔏 一个安全且简单的模板引擎,具有 JSX 的易用性
render
本身是由特质、结构和宏组合而成的,这些组合可以统一并提升组合树形数据结构的体验。这对于 HTML 和 XML 渲染效果最佳,但也适用于其他用法,例如 ReasonML 的终端颜色库 Pastel
。
如何实现?
可渲染的组件是一个实现了 Render
特质的结构。有多个宏可以提供更好的 Renderable 实现
#[component]
用于使用函数定义组件rsx!
用于使用 JSX 易用性组合元素html!
用于组合元素并将它们渲染为字符串
这与...有什么不同?
handlebars
?
Handlebars 是一个很棒的规范,允许我们开发者定义模板,并在不同的语言和框架之间无缝工作。遗憾的是,由于其规范,它不能保证 Rust 的类型安全。这迫使你像在动态类型语言中一样编写测试来验证视图的类型。在 Rust 这样的类型安全语言中不需要这些测试——但 Handlebars 以 JSON 为主,这与 Rust 的类型系统不符。
render
提供了 Rust 提供的相同级别的类型安全,同时不牺牲易用性或速度。
typed-html
?
typed-html
是一个很棒的库。遗憾的是,它专注于 HTML 规范的严格性,不允许任意组合自定义元素。
render
采用不同的方法。目前,HTML 完全未进行类型化。它可以获取任何键和任何字符串值。主要关注自定义组件,因此您可以创建一个可组合和声明性的模板,而不会出现运行时错误。
用法
注意:
render
需要使用nightly
Rust 编译器,因此目前它将具有卫生宏。
这意味着您需要在 lib.rs
/main.rs
的根目录下添加以下功能标志
#![feature(proc_macro_hygiene)]
简单的 HTML 渲染
要将简单的 HTML 片段渲染到 String
中,使用 rsx!
宏生成组件树,并对其调用 render
#![feature(proc_macro_hygiene)]
use render::{rsx, Render};
let tree = rsx! {
<div>
<h1>{"Hello!"}</h1>
<p>{"Hello world!"}</p>
</div>
};
assert_eq!(tree.render(), "<div><h1>Hello!</h1><p>Hello world!</p></div>");
因为这非常常见,还有一个名为 html!
的宏,它调用 rsx!
来生成组件树,然后对其调用 render
。大多数时候,您会发现自己使用 rsx!
宏来组合任意组件,只在需要字符串输出时(如发送响应或生成 Markdown 文件)调用 html!
。
在 Render 中,属性和平面字符串使用 render::html_escaping
模块进行转义。为了使用未转义的值,以便您可以安全地插入原始 HTML,请使用 raw!
宏包围您的字符串
#![feature(proc_macro_hygiene)]
use render::{html, raw};
let tree = html! {
<div>
<p>{"<Hello />"}</p>
<p>{raw!("<Hello />")}</p>
</div>
};
assert_eq!(tree, "<div><p><Hello /></p><p><Hello /></p></div>");
自定义组件
Render 最大的能力是提供类型安全以及自定义可渲染组件。引入新组件就像定义一个返回 Render
值的函数一样简单。
要从其他组件或 HTML 节点构建组件,您可以使用 rsx!
宏,它生成一个 Render
组件树
#![feature(proc_macro_hygiene)]
use render::{component, rsx, html};
#[component]
fn Heading<'title>(title: &'title str) {
rsx! { <h1 class={"title"}>{title}</h1> }
}
let rendered_html = html! {
<Heading title={"Hello world!"} />
};
assert_eq!(rendered_html, r#"<h1 class="title">Hello world!</h1>"#);
如果您仔细观察,您会看到 Heading
函数是
- 使用大写声明的。下面,它生成一个具有相同名称的结构体,并在此结构体上实现
Render
特性。 - 没有返回类型。这是因为为了性能原因,所有内容都写入到写入器中。
可见性 & 组件库
您可能希望将组件存储在项目树中的其他位置,而不是您正在工作的模块中(如果不是在完全不同的模块中)。在这些情况下,定义组件的函数上应用的可见性将向下流入该结构体的所有字段。
例如,如果我们在我们上面的 Heading
组件前添加 "pub"
#[component]
pub fn Heading<'title>(title: &'title str) {
rsx! { <h1 class={"title"}>{title}</h1> }
}
...生成的结构体可能看起来像...
pub struct Heading {
pub title: &'title str
}
从库结构的角度来看,这很重要,需要理解。
完整示例
#![feature(proc_macro_hygiene)]
// A simple HTML 5 doctype declaration
use render::html::HTML5Doctype;
use render::{
// A macro to create components
component,
// A macro to compose components in JSX fashion
rsx,
// A macro to render components in JSX fashion
html,
// A trait for custom components
Render,
};
// This can be any layout we want
#[component]
fn Page<'a, Children: Render>(title: &'a str, children: Children) {
rsx! {
<>
<HTML5Doctype />
<html>
<head><title>{title}</title></head>
<body>
{children}
</body>
</html>
</>
}
}
// This can be a route in Rocket, the web framework,
// for instance.
pub fn some_page(user_name: &str) -> String {
html! {
<Page title={"Home"}>
{format!("Welcome, {}", user_name)}
</Page>
}
}
许可:MIT
依赖项
~1.5MB
~35K SLoC