3 个不稳定版本

0.2.1 2020年6月2日
0.2.0 2020年6月1日
0.1.2 2019年12月20日

#14 in #full-stack

25 每月下载量
2 crate 中使用

MIT 许可证

130KB
3.5K SLoC

Reign View 是一个基于组件的 HTML 模板库,受 Vue.js 模板启发。

此库使得使用模板变得如同甜点般简单。它使用基于 HTML 的模板语法,这些语法是有效的,并且可以被符合规范的浏览器和 HTML 解析器解析 [1][2]。它主要考虑了易用性,其次是未来的扩展性、模块化和定制。

此库还提供了多个辅助工具和功能门,用户可以使用它们进行定制,使库可以直接与或没有 reign_router 一起使用。

有关更多详细信息,请参阅 API 文档

注意:最低支持的 Rust 版本是 1.45.0

目录

快速入门

  1. reign 添加到您的代码库中,省略默认功能并启用 view 功能

    [dependencies]
    reign = { version = "*", features = ["view"], default-features = false }
    
  2. 在您的 main.rs 中初始化模板

    use reign::prelude::*;
    
    // If your templates live under `src/views` folder
    views!("src", "views");
    
  3. src/views/pages/about.html 中编写一个模板

    <p>
      {{ name }}
      <sub>aged {{ age: u8 }}</sub>
    </p>
    
  4. 渲染模板

    use reign::prelude::*;
    
    let (name, age) = ("John", 28);
    
    // The macro automatically captures all the
    // variables it needs, builds the view to display
    // and returns a `String`
    //
    // `pages::about` is the unique path of the above template
    render!(pages::about)
    

工作原理

这个模板库中有多个步骤。

  • 从 HTML 模板构建一个 视图
  • 视图 渲染成一个 String。

构建

假设您已经在前面的部分编写了相应的路径下的模板。

现在,当您通过编写以下内容来初始化模板库时

use reign::prelude::*;

views!("src", "views");

库将展开 views! 宏,变成以下类似的形式

// It will create a module which is always called `views`
mod views {

    // Then it will create a module for `pages` subdirectory
    pub mod pages {

        // Then it will create a struct for the template file
        pub struct About<'a> {

            // It will add a raw string field for every variable
            // found which does not have a type described
            pub name: &'a str,

            // It will add a typed field for every variable found
            // that was given a type (once in a template is enough)
            pub age: u8,
        }

        use std::fmt::{Display, Formatter, Result};

        // Then it will implement std::fmt::Display for it
        impl Display for About<'_> {
            fn fmt(&self, f: &mut Formatter) -> Result {
                write!(f, "{}{}{}{}{}",
                    "<p>\n  ", self.name, "\n  <sub>aged ", self.age, "</sub>\n</p>"
                )
            }
        }
    }
}

上面的展开是近似的。它们在展开的方式或其他内部使用的事物上可能有些小变化。

您可以在下面了解更多关于模板语法的详细信息 这里

渲染

当启用纯视图功能时,当您尝试渲染如下模板时

use reign::prelude::*;

let (name, age) = ("John", 28);

render!(pages::about);

这个库扩展了 render! 宏,使其变成以下的样子

format!("{}", crate::views::pages::About {
    name: name.as_ref(),
    age,
});

它返回以下字符串

<p>
  John
  <sub>aged 28</sub>
<p>

模板语法

在我们开始讨论模板语法之前,让我们就本节的一些术语达成一致,以便稍后更容易引用。

表达式 是 Rust 语言中所有表达式类型的一个自定义子集。你可以在这里了解更多。

模式 是一个自定义的 Rust 模式语法,其中允许的表达式仅限于上面段落中定义的。你可以在这里了解更多。

字段 指的是在初始化模板库时,由 views! 宏构建的用于模板的 struct 的字段。

模板中使用的所有 HTML 样式标签都应该通过自闭合语法或结束标签来关闭。唯一的例外是那些根据 HTML 规范默认可以自闭合的标签,称为 空元素

文本

模板化的最基本形式是使用 "mustache" 语法(双大括号)进行的 "插值"

<span>Message: {{ msg }}</span>

mustache 标签将被替换为 msg 字段 的值。你还可以在 mustache 标签内使用 表达式。任何实现了 std::fmt::Display 的类型都可以作为 mustache 标签定义的 表达式 的最终结果。

<span>Word Count: {{ msg.len() }}</span>

属性

插值也可以用于属性值。

<div title="Application - {{ page_name }}"></div>

如果你想在属性值中嵌入 " 以内的 表达式,你可以遵循 HTML 规范,并用 ' 将值包围。

<div title='Application - {{ "Welcome" }}'></div>

变量属性

如果你想有一个完全由一个 mustache 标签插值,不包含其他内容的属性,你可以这样做。

<div :title="page_name"></div>

属性 title 的值将是 page_name 字段的值。

控制属性

该库不允许在 mustache 标签内作为表达式使用 if 条件和 for 循环。你可以在 HTML 标签上使用控制属性来完成这个任务。

<div !if='page_name == "home"'>Welcome</div>
<div !else>{{ page_name }}</div>

上面的模板根据提供给 !if 控制属性的 表达式 打印第一个或第二个 div

正如预期的那样,也支持 !else-if

for 循环也使用与下面类似的控制属性语法。

<div !for="char in page_name.chars()">{{ char }}</div>

上面的模板为 page_name 字段中的所有字符打印 div 标签。除了控制属性中的名称不同外,!for 需要 模式 in 表达式 语法。

分组元素

由于 !if!for 是属性,它们需要附加到单个标签上。但有时,你可能需要渲染多个元素。在这种情况下,你可以使用 template 标签,它像一个不可见的包装器。

<template v-if="condition">
  <h1>{{ title }}</h1>
  <p>{{ content }}</p>
</template>

模板不允许在根处定义多个元素。这意味着,你可以使用 template 标签来在模板的根处分组元素。

<template>
  <!DOCTYPE html>
  <html></html>
</template>

类 & 样式绑定

待实现

组件

每个模板默认都可以用作组件,因此是可重用的。让我们假设我们有一个模板 src/views/shared/button.html,如下所示

<button :href="href">{{text}}</button>

为此生成的视图如下所示

struct Button<'a> {
    href: &'a str,
    text: &'a str,
}

上述模板可以使用以下语法在其他模板中使用

<navbar>
  <shared:button href="/" text="Dashboard" />
  <shared:button href="/settings" text="Account" />
</navbar>

组件上的属性与上面描述的正常HTML元素上的属性工作方式相同。

任何模板都可以用作组件。我们可以通过使用它的标签引用来引用模板。标签引用可以通过将组件路径中的所有部分转换为大写并使用冒号(:)连接来实现。位于 src/views/users/avatar.html 的模板可以使用 users:avatar,同样,位于 src/views/common/simple/small_icon.html 的模板可以使用 common:simple:small-icon

插槽

与HTML元素一样,将内容传递到组件中通常很有用,例如

<div>
  <shared:button href="/">
    <span class="icon"></span>
    Dashboard
  </shared:button>
</div>

幸运的是,我们可以通过在 button.html 中使用我们自定义的 slot 元素来实现这一点。

<button :href="href">
  <slot></slot>
</button>

上面的 <slot></slot> 会在使用时被替换为 shared:button 元素内部描述的元素。

作用域

当我们在一个组件的插槽中使用变量时,例如

<div>
  <shared:button href="/">{{ link_text }}</shared:button>
</div>

该插槽尝试访问此模板的字段(即相同的 scope)。插槽无法访问 <shared:button> 的字段。例如,尝试访问 href 将不会成功

<div>
  <shared:button href="/">{{ href }}</shared:button>
</div>

它将在模板本身上创建一个 href 字段。

回退

待实现

命名插槽

有时拥有多个插槽很有用。例如,在一个具有以下模板的 <base-layout> 组件中

<div class="container">
  <header>
    <!-- We want header content here -->
  </header>
  <main>
    <!-- We want main content here -->
  </main>
  <footer>
    <!-- We want footer content here -->
  </footer>
</div>

对于这些情况,<slot> 元素有一个特殊的属性 name,可以用来定义额外的插槽

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

没有 name<slot> 通道隐式具有名称 "default"

要为命名插槽提供内容,我们可以在 <template> 上使用特殊属性,提供插槽的名称

<layout:base>
  <template #header>
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template #footer>
    <p>Here's some contact info</p>
  </template>
</layout:base>

现在,<template> 元素内部的全部内容都将传递给相应的插槽。任何未包裹在 <template> 中的内容都假定为默认插槽的内容。

然而,如果您希望明确,仍然可以将默认插槽内容包装在 <template> 中。

辅助工具 & 功能门

Reign 上有多个功能门,帮助用户选择他们想要的库功能。

请参阅 示例 了解如何使用它们。

有关宏的使用信息,请参阅 reign_derive

视图
  • views! 可以用来构建视图。
  • render! 可以用来将视图渲染为字符串。
视图后端
  • views! 可以用来构建视图。
  • 启用 render 辅助函数和 render! 将视图渲染为响应,供 reign_router 后端处理器使用。
  • 启用 redirect 辅助函数,用于 reign_router 后端处理器。
  • 启用 json 辅助函数和 json!reign_router 后端处理器构建响应。

附录

表达式

下面描述了允许的表达式,其中 bop 是二元运算符,而 uop 是一元运算符。... 表示可能的重复。

  • 字面量
  • 标识符
  • [表达式, ...]
  • 表达式 bop 表达式
  • uop 表达式
  • 表达式(表达式, ...)
  • 表达式.标识符
  • 表达式.数字
  • 表达式[表达式]
  • (表达式)
  • [表达式;表达式]
  • (表达式, ...)
  • &表达式
  • 表达式as 类型
  • 表达式: 类型
  • 表达式..表达式
  • 类型 {标识符:表达式, ..表达式, ... }

模式

下面描述了允许的模式,其中 expr 代表上述提到的 表达式... 表示可能的重复。

  • 标识符
  • _
  • &模式
  • 类型 {模式, .., ... }
  • (模式, ...)
  • 类型(模式, .., ...)

注解

  1. 标签名称可以包含 :,这并不完全符合纯 HTML5 规范,但大多数解析器都支持它。
  2. 我们还假设解析器是根据 Web Components 规范制作的。

依赖关系

~3.5–6.5MB
~121K SLoC