NiQin (blog: 泥芹) shared the aphorism --
I returned and saw under the sun, that the race is not to the swift, nor the battle to the strong, neither yet bread to the wise, nor yet riches to men of understanding, nor yet favour to men of skill; but time and chance happeneth to them all. -- 《圣经》

[Rust] 使用 tide、handlebars、rhai、graphql 开发 Rust web 前端(1)- crate 选择及环境搭建

💥 内容涉及著作权,均归属作者本人。若非作者注明,默认欢迎转载:请注明出处,及相关链接。

Summary: 《使用 tide、handlebars、rhai、graphql 开发 Rust web 前端》第一部分:HTTP 服务器端框架、模板引擎库,GraphQL 客户端等 crate 的选择,以及开发环境的搭建和测试。

Topics: rust tide rust-web handlebars rhai graphql-client

目前,web 前端开发方面,通常有两种技术组合:一种是使用模板引擎,主要在服务器端渲染,这种方式对 seo 有较高要求的应用有利;同时,在后续优化方面,也较有优势。另一种则是前端框架,如 yew、react、vue、seed 一类,采用声明式设计;在保证性能下限的前提下,高效且灵活地进行快速开发。

另外,具体到 yew、react、vue、seed 来说,也有所不同:yew、seed 则是 WebAssembly 框架。WebAssembly 是 W3C 组织于 2019 年 12 月下旬才发布的新标准,还处于发展初期。前景或许更广阔一些,但目前落地的应用场景还比较罕见。

前时的文章《Rust 和 Wasm 的融合,使用 yew 构建 WebAssembly 标准的 web 前端》,即是对 Rust 生态中 WebAssembly 框架的实践。放眼整个 web 前端开发,都可以说是比较新颖的技术。但是对于生产环境,其小规模使用,或许都是一个挑战。如果你想使用 Rust 技术栈开发 web 应用,目前还是采用模板引擎的组合,较为稳妥一些。

实践目标

在以前的构建 Rust 异步 GraphQL 服务系列中,分别采用 tide + async-graphql + mongodbactix-web + async-graphql + rbatis + postgresql / mysql 开发了 GraphQL 服务后端。感兴趣的朋友可以参阅博文——

本次实践中,即是基于 Rust 技术生态,采用模板引擎,来实现 Rust web 前端的开发。实践过程中,我们通过 GraphQL 服务后端 API,获取 GraphQL 数据并解析。然后,在页面中,对用户列表、项目列表做以展示。

crate 的选择

Rust 生态中,成熟的模板引擎库非常多。askama 模板引擎的开发者,对下述出现较早的模板库进行了极其简单的测评,有兴趣可以参考 djc/template-benchmarks-rs

  • write!:基于标准库 write! 宏实现
  • handlebars:handlebars 模板的 Rust 实现
  • tera:基于 jinja2/django 模板规范
  • liquid:liquid 模板的 Rust 实现
  • askama:类型安全的、类 jinja 的编译型模板
  • horrorshow:使用 Rust 宏实现的模板
  • ructe:高效、类型安全的编译型模板
  • fomat:使用类 print/write/format 宏实现的小型模板
  • markup:快速、类型安全的模板
  • maud:Rust 实现的编译时 HTML 模板引擎
  • sailfish:简单、小型、快速的模板引擎

上述列表所提及模板,仅为开发较早,askama 模板引擎的开发者对其测评。另外,比较成熟的 Rust 模板引擎还有 mustache 规范的 rustache、rust-mustache,以及微型而极快的 tinytemplate 等等。

本系列文章,笔者选择了 handlebars-rust 模板引擎。评测中,其基准测试结果并不出众,但评测都有其局限性,仅可参考。而 handlebars-rust 对 rhai(Rust 的嵌入式脚本引擎)的支持方面,笔者非常感兴趣,是故选择。

HTTP 服务器框架,笔者选择了轻型的 tide(中文文档)。但是如果你对 actix-web 或者其它服务器端框架更感兴趣,或者想替换也是非常容易的,因为 cookie、GraphQL 客户端等代码都是通用的。

HTTP 客户端框架,笔者选择了 surf。如果你想使用 reqwest,替换仅为一行代码(将发送 GraphQL 请求时的 surf 函数,修改为 reqwest 函数即可)。

项目中,rhai(Rust 的嵌入式脚本引擎),主要用于开发页面脚本,作为 JavaScript 的一个替代方案。

嗯,本次实践用到的主要 crate,大概就是这些。

工程的创建

我们从零开始,进行本次实践。

在我们的实践项目根目录 tide-async-graphql-mongodb 或者 actix-web-async-graphql-rbatis 中,创建新的新的工程 frontend-handlebars。

GraphQL 服务后端,开源在 github,可以访问如下仓库获取源码:

cd tide-async-graphql-mongodb # 或 actix-web-async-graphql-rbatis
cargo new frontend-handlebars --vcs none

同时,需要在根目录的 Cargo.toml(不是 frontend-handlebars 目录中的 Cargo.toml)将 frontend-handlebars 项目添加到 workspace 部分:

[workspace]
members = [
    "./backend", 
    "./frontend-handlebars", 
    "./frontend-yew"
]

开发环境的配置

本文中,我们先进行开发环境的基础配置,整合各个 crate,并运行展示一个包含 handlebars 模板语法的 HTML 文件即可。因此,目前需要的主要 crate 仅为 tide、async-std,以及 handlebars-rust;另外,serdeserde_json crate 也是需要的。其中,async-std 需要启用特性 attributes,而 serde 需要启用特性 derive。我们使用 cargo-edit 工具,将它们加入到 frontend-handlebars 工程中。

cargo add async-std tide serde serde_json handlebars

此时,frontend-handlebars 项目中的 Cargo.toml 文件内容如下:

[package]
name = "frontend-handlebars"
version = "0.1.0"
authors = ["我是谁?"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
async-std = { version = "1.9.0", features = ["attributes"] }
tide = "0.16.0"

serde = { version = "1.0.126", features = ["derive"] }
serde_json = "1.0.64"

handlebars = "4.0.0"

代码开发

本文直接进入 Rust web 的开发演练,对于 Rust 的基础不做提及。

如果你没有 Rust 基础,《通过例子学 Rust》作为入门资料,是个很不错的选择。

对于 handlebars 模板语法,我们也不做提及,官网资料很丰富,或者访问国内同步更新站点

虽然仅是演练,但笔者不建议将代码一股脑写入 main.rs 中。我们划分模块,分层实现。

handlebars 模板

在 frontend-handlebars 目录下,创建放置模板文件的目录:

cd frontend-handlebars
mkdir templates
touch templates/index.html

templates/index.html 是包含 handlebars 语法的模板文件:

<!doctype html>
<html lang="zh">

  <head>
    <title>{{ app_name }}</title>

    <meta charset="utf-8">

    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="author" content="zzy, https://github.com/zzy/tide-async-graphql-mongodb">
  </head>

  <body>
    <center>
      <h1>{{ app_name }} </h1>
      <h3 style="padding-left: 20%;">-- {{ author }}</h3>

      <ul>
        <h2> &nbsp; </h2>
        <h2>
          <li><a href="/users">all users with token from graphql data</a></li>
        </h2>
        <h2>
          <li><a href="/projects">all projects from graphql data</a></li>
        </h2>
        <h2> &nbsp; </h2>
        <h2>
          <li><a href="http://127.0.0.1:8000/graphiql">graphql API</a></li>
        </h2>
      </ul>

    </center>
  </body>

</html>

模板渲染

对模板渲染,很显然是一个通用的处理过程。因此,我们将其抽象,放在通用类模块中。

cd frontend-handlebars/src
mkdir util
touch util/mod.rs util/common.rs

模板的渲染抽象,主要是实现:规范模板路径、注册模板,以及对模板压入渲染数据。util/mod.rsutil/common.rs 2 个文件,代码如下:

util/mod.rs

pub mod common;

util/common.rs

use handlebars::Handlebars;
use serde::Serialize;
use tide::{http::mime::HTML, Body, Response, StatusCode};

pub struct Tpl<'tpl> {
    pub name: String,
    pub reg: Handlebars<'tpl>,
}

impl<'tpl> Tpl<'tpl> {
    pub async fn new(rel_path: &str) -> Tpl<'tpl> {
        let tpl_name = &rel_path.replace("/", "_");
        let abs_path = format!("./templates/{}.html", rel_path);

        // create the handlebars registry
        let mut hbs_reg = Handlebars::new();
        hbs_reg.register_template_file(tpl_name, abs_path).unwrap();

        Tpl {
            name: tpl_name.to_string(),
            reg: hbs_reg,
        }
    }

    pub async fn render<T>(&self, data: &T) -> tide::Result
    where
        T: Serialize,
    {
        let mut resp = Response::new(StatusCode::Ok);
        resp.set_content_type(HTML);
        resp.set_body(Body::from_string(
            self.reg.render(&self.name, data).unwrap(),
        ));

        Ok(resp.into())
    }
}

路由开发

路由,其定义就放在专门的路由模块中:

cd frontend-handlebars/src
mkdir routes
touch routes/mod.rs

也可以再定义一个 home.rs 或者 index.rs,然后将其引入 mod.rs

目前,仅一个页面,所以仅需定义一个路由处理函数,配置一个路由路径即可。所以我们直接将 index 路由处理函数放在 mod.rs 文件中。但是,后续的用户列表、项目列表路由处理,我们会放在各自的模块中。

handlebars 语法规则,可以直接接收 json 格式的数据并解析展示。因此,routes/mod.rs 文件中,我们定义要在模板中展示的数据。代码内容如下:

use tide::{self, Server, Request};
use serde_json::json;

use crate::{State, util::common::Tpl};

pub async fn push_res(app: &mut Server<State>) {

    app.at("/").get(index);
}

async fn index(_req: Request<State>) -> tide::Result {
    let index: Tpl = Tpl::new("index").await;

    // make data and render it
    let data = json!({"app_name": "frontend-handlebars - tide-async-graphql-mongodb", "author": "zzy"});

    index.render(&data).await
}

应用入口

main.rs 作为 web 应用的入口,需要读取路由模块的配置,并将其压入到服务器(Serve)结构体中。这点在 tideactix-web 中,概念是一致的,写法稍有差别。

Statetide 服务器的状态(State)结构体,用于存放一些和服务器具有相同生命周期的对象或值。actix-web 中,概念同样一致。笔者此书仅为示例,表示 tide 有此特性。

mod routes;
mod util;

#[async_std::main]
async fn main() -> Result<(), std::io::Error> {
    // tide logger
    tide::log::start();

    // Initialize the application with state.
    // Something in Tide State
    let app_state = State {};
    let mut app = tide::with_state(app_state);
    // app = push_res(app).await;
    routes::push_res(&mut app).await;

    app.listen(format!("{}:{}", "127.0.0.1", "3000")).await?;

    Ok(())
}

//  Tide application scope state.
#[derive(Clone)]
pub struct State {}

编译和运行

执行 cargo buildcargo run 后,如果你未自定义端口,请在浏览器中打开 http://127.0.0.1:3000 。可以发现,handlebars 模板文件 templates/index.html 中的 HTML 元素:title、h1,以及 h3 的值来自路由处理函数 async fn index(_req: Request<State>)

至此,使用 handlebars 模板的 Rust web 前端开发环境已经搭建成功。

谢谢您的阅读!


Related Articles

  1. [Rust] RustHub.org:基于 Rust-Web 技术栈,及 image-rs、fluent-rs、rhai-script ……
  2. [WebAssembly] yew SSR 服务器端渲染
  3. [Rust] async-std 创建者对于最近“项目是否已死?”,移除对其支持等的答复
  4. [Rust] Rust 1.56.0 版本和 Rust 2021 版次发布,新特性一览,及项目的迁移、升级
  5. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 WebAssembly 博客应用的体验报告
  6. [Rust] Rust 官方周报 399 期(2021-07-14)
  7. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 web 前端(5)- 构建 HTTP 请求、与外部服务器通信的两种方法
  8. [Rust] Rust 官方周报 398 期(2021-07-07)
  9. [Rust] Rust 官方周报 397 期(2021-06-30)
  10. [Rust] Rust 官方周报 396 期(2021-06-23)
  11. [Rust] Rust 官方周报 395 期(2021-06-16)
  12. [Rust] Rust 1.53.0 明日发布,关键新特性一瞥
  13. [Rust] 使用 tide、handlebars、rhai、graphql 开发 Rust web 前端(3)- rhai 脚本、静态/资源文件、环境变量等
  14. [Rust] 使用 tide、handlebars、rhai、graphql 开发 Rust web 前端(2)- 获取并解析 GraphQL 数据
  15. [Rust] 使用 tide、handlebars、rhai、graphql 开发 Rust web 前端(1)- crate 选择及环境搭建
  16. [Rust] Rust 官方周报 394 期(2021-06-09)
  17. [Rust] Rust web 前端库/框架评测,以及和 js 前端库/框架的比较
  18. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 web 前端(4)- 获取 GraphQL 数据并解析
  19. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 web 前端(3)- 资源文件及小重构
  20. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 WebAssembly 标准的 web 前端(2)- 组件和路由
  21. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 WebAssembly 标准的 web 前端(1)- 起步及 crate 选择
  22. [Rust] Rust 官方周报 393 期(2021-06-02)
  23. [Rust] Rust 官方周报 392 期(2021-05-26)
  24. [Rust] Rust 中,对网址进行异步快照,并添加水印效果的实践
  25. [Rust] Rust 官方周报 391 期(2021-05-19)
  26. [Rust] Rust,风雨六载,砥砺奋进
  27. [Rust] 为什么我们应当将 Rust 用于嵌入式开发?
  28. [Rust] Rust 官方周报 390 期(2021-05-12)
  29. [Rust] Rust + Android 的集成开发设计
  30. [Rust] Rust 1.52.1 已正式发布,及其新特性详述
  31. [Rust] 让我们用 Rust 重写那些伟大的软件吧
  32. [Rust] Rust 1.52.0 已正式发布,及其新特性详述
  33. [Rust] Rust 官方周报 389 期(2021-05-05)
  34. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务(4) - 变更服务,以及小重构
  35. [Rust] Rust 1.52.0 稳定版预发布测试中,关键新特性一瞥
  36. [Rust] Rust 生态中,最不知名的贡献者和轶事
  37. [Rust] Rust 基金会迎来新的白金会员:Facebook
  38. [Rust] Rustup 1.24.1 已官宣发布,及其新特性详述
  39. [Rust] Rust 官方周报 388 期(2021-04-28)
  40. [Rust] Rust 官方周报 387 期(2021-04-21)
  41. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(4)- 变更服务,以及第二次重构
  42. [Rust] Rustup 1.24.0 已官宣发布,及其新特性详述
  43. [Rust] basedrop:Rust 生态中,适用于实时音频的垃圾收集器
  44. [Rust] Rust 编译器团队对成员 Aaron Hill 的祝贺
  45. [Rust] Jacob Hoffman-Andrews 加入 Rustdoc 团队
  46. [机器人] 为什么应将 Rust 引入机器人平台?以及机器人平台的 Rust 资源推荐
  47. [Rust] rust-lang.org、crates.io,以及 docs.rs 的管理,已由 Mozilla 转移到 Rust 基金会
  48. [Rust] Rust 官方周报 386 期(2021-04-14)
  49. [Rust] Rust 编译器(Compiler)团队 4 月份计划 - Rust Compiler April Steering Cycle
  50. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务(3) - 重构
  51. [Rust] 头脑风暴进行中:Async Rust 的未来熠熠生辉
  52. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务(2) - 查询服务
  53. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务 - 起步及 crate 选择
  54. [Rust] Rust 2021 版本特性预览,以及工作计划
  55. [Rust] Rust 用在生产环境的 42 家公司
  56. [Rust] 构建最精简的 Rust Docker 镜像
  57. [Rust] Rust 官方周报 385 期(2021-04-07)
  58. [Rust] 使用 Rust 做异步数据采集的实践
  59. [Rust] Android 支持 Rust 编程语言,以避免内存缺陷
  60. [Rust] Android 平台基础支持转向 Rust
  61. [Rust] Android 团队宣布 Android 开源项目(AOSP),已支持 Rust 语言来开发 Android 系统本身
  62. [Rust] RustyHermit——基于 Rust 实现的下一代容器 Unikernel
  63. [Rust] Rustic:完善的纯粹 Rust 技术栈实现的国际象棋引擎,多平台支持(甚至包括嵌入式设备树莓派 Raspberry Pi、Buster)
  64. [Rust] Rust 迭代器(Iterator trait )的要诀和技巧
  65. [Rust] 使用 Rust 极致提升 Python 性能:图表和绘图提升 24 倍,数据计算提升 10 倍
  66. [Rust] 【2021-04-03】Rust 核心团队人员变动
  67. [Rust] Rust web 框架现状【2021 年 1 季度】
  68. [Rust] Rust 官方周报 384 期(2021-03-31)
  69. [Rust] Rust 中的解析器组合因子(parser combinators)
  70. [生活] 毕马威(KPMG)调查报告:人工智能的实际采用,在新冠疫情(COVID-19)期间大幅提升
  71. [Python] HPy - 为 Python 扩展提供更优秀的 C API
  72. [Rust] 2021 年,学习 Rust 的网络资源推荐(2)
  73. [Rust] 2021 年,学习 Rust 的网络资源推荐
  74. [生活] 况属高风晚,山山黄叶飞——彭州葛仙山露营随笔
  75. [Rust] Rust 1.51.0 已正式发布,及其新特性详述
  76. [Rust] 为 Async Rust 构建共享的愿景文档—— Rust 社区的讲“故事”,可获奖
  77. [Rust] Rust 纪元第 382 周最佳 crate:ibig 的实践,以及和 num crate 的比较
  78. [Rust] Rust 1.51.0 稳定版本改进介绍
  79. [Rust] Rust 中将 markdown 渲染为 html
  80. [生活] 国民应用 App 的用户隐私数据窥探
  81. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(3)- 重构
  82. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(2)- 查询服务
  83. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(1)- 起步及 crate 选择
  84. [Rust] Rust 操控大疆可编程 tello 无人机

Topics

rust(84)

graphql(17)

rust-官方周报(17)

webassembly(16)

wasm(10)

tide(9)

async-graphql(9)

yew(9)

rust-web(8)

rust-官方博客(8)

this-week-in-rust(6)

mysql(5)

actix-web(5)

rbatis(5)

android(4)

mongodb(3)

json-web-token(3)

jwt(3)

cargo(3)

技术延伸(3)

rust-wasm(3)

trunk(3)

handlebars(3)

rhai(3)

async-std(3)

用户隐私(2)

学习资料(2)

python(2)

ai(2)

人工智能(2)

postgresql(2)

rust-compiler(2)

rust-基金会(2)

rust-foundation(2)

rustup(2)

rust-toolchain(2)

rust-工具链(2)

rust-游戏开发(2)

rust-区块链(2)

rust-2021(2)

graphql-client(2)

surf(2)

rust-game(2)

rusthub(2)

tello(1)

drone(1)

无人机(1)

隐私数据(1)

markdown(1)

html(1)

crate(1)

async(1)

异步(1)

旅游(1)

不忘生活(1)

葛仙山(1)

hpy(1)

python-扩展(1)

正则表达式(1)

解析器组合因子(1)

组合器(1)

regular-expression(1)

parser-combinator(1)

regex(1)

官方更新(1)

rust-工作招聘(1)

rust-技术资料(1)

rust-周最佳-crate(1)

rust-web-框架(1)

rust-web-framework(1)

rust-核心团队(1)

rust-core-team(1)

rust-language-team(1)

pyo3(1)

rust-python-集成(1)

python-性能改进(1)

迭代器(1)

iterator-trait(1)

国际象棋(1)

chess(1)

游戏引擎(1)

game-engine(1)

虚拟化(1)

unikernel(1)

rustyhermit(1)

linux(1)

virtualization(1)

sandboxing(1)

沙箱技术(1)

数据采集(1)

异步数据采集(1)

docker(1)

镜像(1)

生产环境(1)

rust-评价(1)

rust-2021-edition(1)

rust-2021-版本(1)

graphql-查询(1)

vision-doc(1)

愿景文档(1)

代码重构(1)

steering-cycle(1)

方向周期(1)

隐私声明(1)

机器人(1)

robotics(1)

rustdoc(1)

rust-编译器(1)

实时音频(1)

real-time-audio(1)

变更服务(1)

mutation(1)

查询服务(1)

query(1)

rust-贡献者(1)

rust-轶事(1)

rust-稳定版(1)

rust-预发布(1)

rust-测试(1)

安全编程(1)

可信计算(1)

安全代码(1)

secure-code(1)

rust-android-integrate(1)

rust-embedded(1)

rust-嵌入式(1)

rust-生产环境(1)

rust-production(1)

网页快照(1)

网页截图(1)

水印效果(1)

图片水印(1)

yew-router(1)

css(1)

web-前端(1)

wasm-bindgen(1)

区块链(1)

blockchain(1)

dotenv(1)

标识符(1)

rust-1.53.0(1)

rust-1.56.0(1)

rust-项目升级(1)

异步运行时(1)

ssr(1)

tokio(1)

warp(1)

reqwest(1)

graphql-rust(1)


Elsewhere

- Open Source
  1. github/zzy
  2. github/sansx
- Learning & Studying
  1. Rust 学习资料 - 泥芹