11个不稳定版本 (3个破坏性更新)
0.4.1 | 2023年4月6日 |
---|---|
0.4.0 | 2023年4月6日 |
0.3.1 | 2023年4月2日 |
0.2.5 | 2023年4月2日 |
0.1.0 | 2021年11月26日 |
#170 在 模板引擎 中
每月67次 下载
90KB
1K SLoC
laby
laby是一个小型宏库,用于在Rust中编写HTML模板。 文档
let n = html!(
head!(
title!("laby"),
),
body!(
p!("Hello, world!"),
),
);
let s = render!(DocType::HTML5, n);
<!DOCTYPE html><html><head><title>laby</title></head><body><p>Hello, world!</p></body></html>
lib.rs
:
laby是一个小型 宏 库,用于在Rust中编写快速HTML模板。它关注以下三个方面
- 简单性:laby具有最少的依赖项,无需任何配置即可直接运行,并且可以根据需要轻松扩展以添加额外功能。
- 性能:laby生成专用代码以生成HTML。在运行时不需要进行堆分配,除了将生成的HTML渲染到的缓冲区外。任何涉及额外堆分配的操作都是可选的。所有渲染代码都在编译时进行静态类型检查并内联以优化性能。
- 熟悉性:laby提供可以接受任何有效Rust代码并扩展为常规Rust代码的宏;学习新的领域特定语言进行HTML模板编写不是必需的。宏可以嵌套、组合、格式化,并通过rustfmt进行格式化,像常规Rust代码一样分离成组件并由函数返回。
laby的大部分高性能代码是从sailfish继承的,这是一个为Rust设计的极快的HTML模板引擎。然而,sailfish使用特殊的语法处理HTML模板文件,而laby提供直接嵌入到您的代码中的宏。选择哪个库取决于您的编码风格和个人偏好。
laby针对 Rust稳定版,并支持带无_std的嵌入式环境。无需进行配置。
安装
在您的项目中,将以下行添加到 Cargo.toml
文件中的 [dependencies]
部分
[dependencies]
laby = "0.4"
此外,您可能希望像这样将laby导入到您的代码中
use laby::*;
这只是为了方便,因为laby导出大量的宏,每个宏代表一个HTML标签。当然,也可以单独导入你使用的宏。本指南的其余部分假设您已经导入了必要的宏。
laby不提供对流行Web框架的集成支持。它返回一个普通的String
作为渲染结果,因此鼓励您编写自己的宏,将那个String
写入响应流。大多数Web框架都可以开箱即用完成这项任务。
基础
laby提供在编译时生成专用Rust代码的过程宏,这些代码在运行时渲染时生成HTML代码。为了有效地使用laby,理解它是如何转换您的代码的非常重要。考虑以下示例。
# use laby::*;
// construct a tree of html nodes
let n = html!(
head!(
title!("laby"),
),
body!(
class = "dark",
p!("hello, world"),
),
);
// convert the tree into a string
let s = render!(n);
// check the result
assert_eq!(s, "\
<html>\
<head>\
<title>laby</title>\
</head>\
<body class=\"dark\">\
<p>hello, world</p>\
</body>\
</html>\
");
上面的代码使用宏html!
、head!
、title!
、body!
和[p!
]来构建基本的HTML结构。然后,使用render!
宏将树转换为String
表示形式。结果与另一个字符串进行比较,该字符串被扩展到多行以提高可读性。
注意,节点子代作为常规位置参数传递,而节点属性作为赋值表达式指定。这是完全有效的Rust语法,这意味着可以使用rustfmt进行格式化。
在底层,laby将上述代码转换为类似以下代码
# use laby::*;
let n = {
struct _html {}
impl Render for _html {
#[inline]
fn render(self, buf: &mut laby::internal::Buffer) {
buf.push_str("<html><head><title>laby</title></head><body class=\"dark\"><p>hello, world</p></body></html>");
}
}
_html {}
};
let s = render!(n);
// assert_eq!(s, ...);
本质上,这就是laby宏所做的一切;它们只是为节点树声明一个新的专用结构体,为该结构体实现Render
trait,构建该结构体,并返回构建的值。
当此代码编译为发布版本时,所有包装代码都会被剥离,渲染代码将被内联,执行时将类似于以下内容
# use laby::*;
let mut buf = laby::internal::Buffer::new();
buf.push_str("<html><head><title>laby</title></head><body class=\"dark\"><p>hello, world</p></body></html>");
let s = buf.into_string();
// assert_eq!(s, ...);
模板化
laby接受任何有效的表达式作为属性名称和值以及子代节点,并且可以像常规代码一样访问局部作用域中的变量。它不仅限于只接受字符串字面量。
唯一的要求是表达式评估的结果必须实现Render
trait。请参阅外部实现列表以了解哪些类型实现了此trait。评估后的值存储在专用结构体中,并在render!
宏被调用时进行渲染。考虑以下示例。
// retrieve an article from a database
let title = "laby";
let content = "hello, 'world'";
let date = "2030-01-01";
// construct a tree of nodes, with templated expressions
let n = article!(
class = format!("date-{}", date),
h1!({
let mut title = title.to_owned();
title.truncate(30);
title
}),
p!(content),
);
// convert the tree into a string
let s = render!(n);
// check the result
assert_eq!(s, "\
<article class=\"date-2030-01-01\">\
<h1>laby</h1>\
<p>hello, 'world'</p>\
</article>\
");
上面的代码构建了一个带有标题、内容和类属性的模板基本HTML结构。
class
属性:展开和评估format!
宏表达式。<h1>
节点:评估一个截断标题至最多三十个字符的表达式。<p>
节点:评估一个简单的局部变量表达式。
注意,这些表达式是在节点构建的地方(即 let n = ...
)进行评估的,而不是在调用 render!
宏的地方。
此外,文章内容中的撇号被HTML实体 '
转义。默认情况下,laby会对所有模板表达式进行转义,除非使用 raw!
宏。
在底层,laby将上述代码转换为类似以下代码
let title = "laby";
let content = "hello, 'world'";
let date = "2030-01-01";
let n = {
struct _article<T1, T2, T3> { t1: T1, t2: T2, t3: T3 }
impl<T1, T2, T3> Render for _article<T1, T2, T3>
where T1: Render, T2: Render, T3: Render {
#[inline]
fn render(self, buf: &mut laby::internal::Buffer) {
buf.push_str("<article class=\"");
self.t1.render(buf); // date
buf.push_str("\"><h1>");
self.t2.render(buf); // title
buf.push_str("</h1><p>");
self.t3.render(buf); // content
buf.push_str("</p></article>");
}
}
_article {
t1: format!("date-{}", date),
t2: {
let mut title = title.to_owned();
title.truncate(30);
title
},
t3: content
}
};
let s = render!(n);
// assert_eq!(s, ...);
注意,生成的专用结构的字段是泛型化的,它们覆盖了模板表达式。当该结构被构建时(即 _article { ... }
),编译器能够从字段赋值中推断泛型类型参数并对结构进行单态化。如果所有字段表达式都评估为实现了 Render
特性的值,那么该特性也将为生成的结构实现,允许它通过 render!
进行渲染。
组件化
编写用于渲染整个HTML文档的大模板很快就会变得难以管理且难以维护,因此通常有必要将文档拆分成几个较小的组件。针对这个问题有两种流行的技术:包含 和 继承。laby支持这两种模式,使用Rust提供的高级语言功能。
在实践中,这些模式经常混合使用,以形成一个完整且一致的文档。以下将探讨这两种方法的示例。
模板继承
这是一个 自顶向下 的方法,它将大文档分解为小组件。这导致了一种结构一致但僵化,难以轻松扩展或更改的结构。
# use laby::*;
// a large template that takes small components
fn page(title: impl Render, header: impl Render, body: impl Render) -> impl Render {
html!(
head!(
title!(title),
),
body!(
header!(header),
main!(body),
),
)
}
// a component that *inherits* a large template
fn home() -> impl Render {
page(
"Home",
h1!("About laby"),
p!("laby is an HTML macro library for Rust."),
)
}
assert_eq!(render!(home()), "\
<html>\
<head>\
<title>Home</title>\
</head>\
<body>\
<header>\
<h1>About laby</h1>\
</header>\
<main>\
<p>laby is an HTML macro library for Rust.</p>\
</main>\
</body>\
</html>\
");
模板包含
这是一个 自底向上 的方法,它将小组件合并成大文档。这导致了一种灵活但可能不一致的结构,也可能导致更多的样板代码。
# use laby::*;
// small individual components
fn title() -> impl Render {
"Home"
}
fn header() -> impl Render {
h1!("About laby")
}
fn body() -> impl Render {
p!("laby is an HTML macro library for Rust.")
}
// a large component that *includes* the small components
fn home() -> impl Render {
html!(
head!(
title!(title()),
),
body!(
header!(header()),
main!(body()),
),
)
}
assert_eq!(render!(home()), "\
<html>\
<head>\
<title>Home</title>\
</head>\
<body>\
<header>\
<h1>About laby</h1>\
</header>\
<main>\
<p>laby is an HTML macro library for Rust.</p>\
</main>\
</body>\
</html>\
");
命名参数
有时组件会变得很大,接受一个很长的位置参数列表,这会损害可读性。laby提供了一个名为 #[laby]
的属性宏,它允许您使用显式命名的参数和可选值调用任意函数,类似于HTML宏。
要启用支持,只需在组件函数之前添加此属性,然后使用生成的宏调用它。
#[laby]
fn page(title: impl Render, header: impl Render, body: impl Render) -> impl Render {
html!(
head!(
title!(title),
),
body!(
header!(header),
main!(body),
),
)
}
#[laby]
fn home() -> impl Render {
// `page` function called using the generated `page!` macro
page!(
title = "Home",
header = h1!("About laby"),
body = p!("laby is an HTML macro library for Rust."),
)
}
扩展
laby 可以通过简单地实现 Render
特性来扩展,这是一个表示渲染操作最小单位的低级特性。如果 laby 提供的现成功能不能满足您的特定需求,或者 laby 没有为所需的类型提供 Render
实现,那么自己实现这个特性可能是一个可行的解决方案。
创建扩展的一般模式如下
- 编写一个结构体,存储您的渲染操作所需的所有必要数据。
- 为该结构体实现
Render
特性。 - 提供一个简单、简短的宏,方便地构建该结构体。
事实上,iter!
、raw!
和 disp!
宏就是以这种方式实现的。它们并不是魔法;它们仅仅是 laby 核心渲染系统的扩展。您甚至可以忽略 laby 的 HTML 宏,并编写自己的转换来实现 Render
特性。
许可证
laby 由 chiya.dev 编写,许可协议为 MIT 许可证。部分代码来自 sailfish,由 Ryohei Machida 编写,同样采用 MIT 许可证。HTML 标签的文档来自 MDN,许可协议为 CC-BY-SA 2.5。
依赖项
~2MB
~49K SLoC