Rust动态模板引擎rocket_dyn_templates的使用,支持快速构建类型安全的Web页面渲染

Rust动态模板引擎rocket_dyn_templates的使用,支持快速构建类型安全的Web页面渲染

这个crate为Rocket添加了动态模板渲染支持。它能自动发现模板,提供Responder来渲染模板,并在调试模式下自动重新加载模板。目前支持Handlebars和Tera。

使用方法

  1. 启用与您选择的模板引擎对应的rocket_dyn_templates特性:
[dependencies.rocket_dyn_templates]
version = "0.2.0"
features = ["handlebars", "tera"]
  1. 在可配置的template_dir目录(默认:{rocket_root}/templates)中编写Handlebars(.hbs)和/或Tera(.tera)模板文件。

  2. 附加Template::fairing()并使用Template::render()返回一个Template,提供模板文件名(减去最后两个扩展名):

use rocket_dyn_templates::{Template, context};

#[get("/")]
fn index() -> Template {
    Template::render("template-name", context! { field: "value" })
}

#[launch]
fn rocket() -> _ {
    rocket::build().attach(Template::fairing())
}

完整示例

以下是一个完整的Rocket web应用示例,使用rocket_dyn_templates渲染动态模板:

// Cargo.toml
[dependencies]
rocket = "0.5.0"
rocket_dyn_templates = { version = "0.2.0", features = ["tera"] }
// src/main.rs
#[macro_use] extern crate rocket;

use rocket_dyn_templates::{Template, context};

// 定义路由
#[get("/")]
fn index() -> Template {
    Template::render("index", context! {
        title: "Rocket Templates",
        items: vec!["Item 1", "Item 2", "Item 3"],
    })
}

#[get("/user/<name>")]
fn user(name: &str) -> Template {
    Template::render("user", context! {
        username: name,
    })
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .attach(Template::fairing()) // 添加模板支持
        .mount("/", routes![index, user])
}
<!-- templates/index.tera -->
<!DOCTYPE html>
<html>
<head>
    <title>{{ title }}</title>
</head>
<body>
    <h1>{{ title }}</h1>
    <ul>
        {% for item in items %}
        <li>{{ item }}</li>
        {% endfor %}
    </ul>
</body>
</html>
<!-- templates/user.tera -->
<!DOCTYPE html>
<html>
<head>
    <title>User {{ username }}</title>
</head>
<body>
    <h1>Welcome, {{ username }}!</h1>
</body>
</html>

这个示例展示了:

  1. 创建一个简单的Rocket web应用
  2. 使用Tera模板引擎渲染动态页面
  3. 传递上下文数据到模板
  4. 处理路由参数
  5. 使用列表渲染

模板文件应放在项目的templates目录下,Rocket会自动发现它们并在开发模式下热重载。

完整示例demo

下面是一个更完整的示例,展示如何使用rocket_dyn_templates构建一个博客系统:

// Cargo.toml
[dependencies]
rocket = "0.5.0"
rocket_dyn_templates = { version = "0.2.0", features = ["tera"] }
serde = { version = "1.0", features = ["derive"] }
// src/main.rs
#[macro_use] extern crate rocket;

use rocket_dyn_templates::{Template, context};

// 博客文章结构体
#[derive(Debug)]
struct Post {
    id: u32,
    title: String,
    content: String,
    published: bool,
}

// 模拟数据库
fn get_all_posts() -> Vec<Post> {
    vec![
        Post {
            id: 1,
            title: "Rocket入门".to_string(),
            content: "学习Rocket框架的基础知识".to_string(),
            published: true,
        },
        Post {
            id: 2,
            title: "模板引擎".to_string(),
            content: "使用rocket_dyn_templates渲染页面".to_string(),
            published: true,
        },
    ]
}

// 首页路由 - 显示所有文章
#[get("/")]
fn index() -> Template {
    let posts = get_all_posts();
    Template::render("blog/index", context! {
        title: "我的博客",
        posts: posts,
    })
}

// 文章详情页
#[get("/post/<id>")]
fn post(id: u32) -> Option<Template> {
    get_all_posts()
        .into_iter()
        .find(|p| p.id == id && p.published)
        .map(|post| {
            Template::render("blog/post", context! {
                title: &post.title,
                post: post,
            })
        })
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .attach(Template::fairing())
        .mount("/", routes![index, post])
}
<!-- templates/blog/index.tera -->
<!DOCTYPE html>
<html>
<head>
    <title>{{ title }}</title>
</head>
<body>
    <h1>{{ title }}</h1>
    <div class="posts">
        {% for post in posts %}
        <article>
            <h2><a href="/post/{{ post.id }}">{{ post.title }}</a></h2>
            <p>{{ post.content|truncate(length=50) }}</p>
        </article>
        {% endfor %}
    </div>
</body>
</html>
<!-- templates/blog/post.tera -->
<!DOCTYPE html>
<html>
<head>
    <title>{{ title }}</title>
</head>
<body>
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
    <a href="/">返回首页</a>
</body>
</html>

这个更完整的示例展示了:

  1. 使用结构体组织数据
  2. 实现简单的文章列表和详情页
  3. 使用Tera模板的过滤器功能(truncate)
  4. 处理可选路由(返回Option<Template>)
  5. 更复杂的模板组织结构

1 回复

Rust动态模板引擎rocket_dyn_templates使用指南

rocket_dyn_templates是Rocket框架提供的动态模板引擎集成,支持多种模板语言,允许开发者快速构建类型安全的Web页面渲染。

主要特性

  • 支持多种模板引擎:Handlebars、Tera等
  • 类型安全的模板变量传递
  • 与Rocket框架无缝集成
  • 自动重新加载开发环境中的模板

基本使用方法

1. 添加依赖

首先在Cargo.toml中添加依赖:

[dependencies]
rocket = "0.5.0"
rocket_dyn_templates = { version = "0.1.0", features = ["tera"] }

2. 基本配置

#[macro_use] extern crate rocket;

use rocket_dyn_templates::Template;

#[launch]
fn rocket() -> _ {
    rocket::build()
        .attach(Template::fairing())
}

3. 创建模板文件

在项目根目录下创建templates文件夹,然后添加模板文件,例如hello.html.tera:

<!-- templates/hello.html.tera -->
<h1>Hello, {{ name }}!</h1>
<p>Your user ID is: {{ user_id }}</p>

4. 渲染模板

#[get("/hello/<name>/<user_id>")]
fn hello(name: &str, user_id: u32) -> Template {
    let context = std::collections::HashMap::<String, String>::from([
        ("name".into(), name.into()),
        ("user_id".into(), user_id.to_string()),
    ]);
    
    Template::render("hello", &context)
}

高级用法

使用结构体传递上下文

#[derive(serde::Serialize)]
struct HelloContext {
    name: String,
    user_id: u32,
    items: Vec<String>,
}

#[get("/hello/<name>/<user_id>")]
fn hello(name: String, user_id: u32) -> Template {
    let context = HelloContext {
        name,
        user_id,
        items: vec!["Item 1".into(), "Item 2".into()],
    };
    
    Template::render("hello", &context)
}

对应的模板hello.html.tera:

<h1>Hello, {{ name }}!</h1>
<p>User ID: {{ user_id }}</p>
<ul>
{% for item in items %}
    <li>{{ item }}</li>
{% endfor %}
</ul>

自定义模板目录

use rocket_dyn_templates::{Template, Metadata};

#[launch]
fn rocket() -> _ {
    rocket::build()
        .attach(Template::custom(|engines| {
            engines.tera.dirs.push("custom_templates".into());
        }))
}

模板继承

tera支持模板继承,可以创建基础模板:

<!-- templates/base.html.tera -->
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}{% endblock title %}</title>
</head>
<body>
    {% block content %}{% endblock content %}
</body>
</html>

然后扩展它:

<!-- templates/page.html.tera -->
{% extends "base.html" %}

{% block title %}My Page{% endblock title %}

{% block content %}
<h1>Welcome to my page!</h1>
{% endblock content %}

错误处理

当模板渲染失败时,Rocket会自动返回500错误。你也可以自定义错误处理:

#[get("/hello")]
fn hello() -> Result<Template, std::io::Error> {
    let context = /* ... */;
    Template::render("hello", &context)
        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
}

性能提示

  1. 在生产环境中,模板会被缓存,因此无需担心重复解析
  2. 使用release模式编译以获得最佳性能
  3. 对于复杂的模板逻辑,考虑将部分逻辑移到Rust代码中

rocket_dyn_templates提供了类型安全且高效的Web页面渲染方案,是构建Rocket Web应用的理想选择。

完整示例Demo

下面是一个完整的Rocket应用示例,展示了如何使用rocket_dyn_templates

// src/main.rs
#[macro_use] extern crate rocket;

use rocket_dyn_templates::Template;
use serde::Serialize;

// 定义上下文结构体
#[derive(Serialize)]
struct PageContext {
    title: String,
    content: String,
    items: Vec<String>,
}

#[get("/")]
fn index() -> Template {
    let context = PageContext {
        title: "首页".to_string(),
        content: "欢迎来到我的网站!".to_string(),
        items: vec!["项目1".to_string(), "项目2".to_string(), "项目3".to_string()],
    };
    Template::render("index", &context)
}

#[get("/about")]
fn about() -> Template {
    let context = PageContext {
        title: "关于我们".to_string(),
        content: "这是关于我们的页面".to_string(),
        items: vec!["团队".to_string(), "历史".to_string(), "联系方式".to_string()],
    };
    Template::render("about", &context)
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .mount("/", routes![index, about])
        .attach(Template::fairing())
}

模板文件 templates/base.html.tera:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}{% endblock title %}</title>
    <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; margin: 0 auto; max-width: 800px; padding: 20px; }
        nav { background: #f4f4f4; padding: 10px; margin-bottom: 20px; }
        nav a { margin-right: 15px; text-decoration: none; }
    </style>
</head>
<body>
    <nav>
        <a href="/">首页</a>
        <a href="/about">关于</a>
    </nav>
    
    <h1>{% block heading %}{% endblock heading %}</h1>
    
    <div class="content">
        {% block content %}{% endblock content %}
    </div>
    
    {% if items %}
    <h2>项目列表</h2>
    <ul>
        {% for item in items %}
        <li>{{ item }}</li>
        {% endfor %}
    </ul>
    {% endif %}
</body>
</html>

模板文件 templates/index.html.tera:

{% extends "base.html" %}

{% block title %}我的网站 - 首页{% endblock title %}
{% block heading %}欢迎来到我的网站{% endblock heading %}

{% block content %}
<p>这是一个使用Rocket和Tera模板引擎构建的网站示例。</p>
<p>当前时间: {{ now() }}</p>
{% endblock content %}

模板文件 templates/about.html.tera:

{% extends "base.html" %}

{% block title %}我的网站 - 关于我们{% endblock title %}
{% block heading %}关于我们{% endblock heading %}

{% block content %}
<p>我们是一个专注于Rust开发的团队。</p>
<p>这个网站展示了Rocket框架与Tera模板引擎的集成使用。</p>
{% endblock content %}

这个完整示例展示了:

  1. 使用结构体传递模板上下文
  2. 模板继承机制
  3. 条件渲染和循环
  4. 多个路由的模板渲染
  5. 基本的HTML结构和样式

要运行这个示例:

  1. 创建上面列出的所有文件
  2. 确保Cargo.toml中包含正确的依赖
  3. 运行cargo run启动服务器
  4. 访问http://localhost:8000查看效果
回到顶部