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

MIT 许可证

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>&lt;Hello /&gt;</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