[WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 web 前端(3)- 资源文件及小重构
💥 内容涉及著作权,均归属作者本人。若非作者注明,默认欢迎转载:请注明出处,及相关链接。
Summary: 《Rust 和 Wasm 的融合,使用 yew 构建 WebAssembly 标准的 web 前端》系列文章第三部分:引入 css 样式表、图像等资源文件到 trunk 构建路径;以及精简 html! 宏中代码,提取为函数的代码重构等。
Topics: rust graphql webassembly wasm yew css
前两篇文章《起步及 crate 选择》和《组件和路由》中,我们介绍了选型原因,搭建了 yew 的基本开发环境,并进行了最基础的组件和路由编码。并且和 yew 中文文档的翻译者 sansx 老师及一些感兴趣的朋友进行了友好而热烈的交流。
关于交流心得,笔者感觉有必要提及一下,作为一个即要走路也要看路的技术认知:
- 关于
html!
宏中的<>……</>
,这是因为html!
宏仅能有一个根标签元素。<>……</>
充当了一个根标签,输出实际上是空的。另外,html!
宏中的标签必须闭合,即使 html5 标准中不需要/>
的自闭合标签,也不能省略/>
。如<img src="path" />
。 - yew 生产环境的应用。笔者仅是 yew 的初学者,理解不很恰当。根据对官方 API 文档的理解,个人认为当前版本(yew 0.18)用于生产环境,是一个不小的挑战(包括开发和维护)。但从项目源码、issues 讨论,以及路线规划来看,个人认为下个版本(yew 0.19),差强人意。等待发布后,yew 0.19 用于个人或者团队的生产环境,是可以接受的。笔者也有此计划。
- ssr 或者 seo 方面,yew 官方有计划,但未有实质进度。但笔者认为影响不大,网上几年前就有文章给出了结论:新时代的搜索引擎(Google、Yahoo、Bing、DuckDuckGo 等),能够像现代浏览器一样访问网站,能很好的抓取动态渲染后的内容,不用担心使用 yew 之类的框架而导致 seo 出现问题。几年过去了,搜索引擎的技术进步应该很大。再者,笔者认为现在信息传播的方式已经有所改变,国内尤为明显。最后,当国外搜索引擎已经收录大量中文站点的内容时,某些国内搜索引擎,却仅是首页甚至是未有收录;这样的情形,即使技术方面对 seo 很适配,估计也是不能解决收录问题的。
- Rust 官方周报 393 期中有一篇技术,是关于 Rust + WebAssembly 为 deno 开发插件系统的,看起来前景很不错。基于 WebAssembly 的性能和特性,如果插件足够通用,说不定可发展为一个独立的职业。
前两篇文章中,我们实现的界面是非常简陋的,没有引入任何样式、图像等 web 应用必不可少的资源文件。本篇文章中,我们将实践如何对 yew 组件使用样式,组件包含图片等。严格来说,这部分是属于构建工具 trunk
的知识。trunk
工具在首篇文章《起步及 crate 选择》中已经提及,是完全的 Rust 技术栈开发,不同于 wasm-pack 那样需要 node 环境。其在样式方面,支持 css/sass/scss(scss 实质是 sass3 及之后的升级版,目前使用更广一些),我们都将进行实践。图像方面,笔者分别引入 icon 和在组件中放置 <img>
标签以作示例。其它 js 和数据等资源文件,未有设计,但使用和图像是类同的。
引入样式表
笔者在 frontend-yew
目录中,创建如下目录和结构,放置资源文件:
mkdir -p assets/{css, imgs, js, data}
cd assets/css
touch style.css style.sass style.scss
css 代码
我们分别有 css、sass,以及 scss,仅是为验证 trunk
对其都可以编译。
style.css
.home {
background-color: red;
}
style.sass
$users-color: blue
.users
background-color: $users-color;
style.scss
.logo-title {
line-height: 40px;
display:flex;
align-items: center;
}
.nav {
line-height: 30px;
display:flex;
align-items: center;
margin-bottom: 20px;
font-weight: bold;
}
$projects-color: green;
.projects {
background-color: $projects-color;
}
将样式表加入 trunk 构建路径
trunk 工具构建时,资源文件通过 <link>
标签引入,但需要声明 data-trunk
。我们要将上述三个样式表加入构建路径,在 index.html
文件中的 <head>
标签内,加入它们的路径:
<!doctype html>
<html lang="zh">
<head>
<title>tide-async-graphql-mongodb - frontend - yew</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">
<link data-trunk rel="css" href="assets/css/style.css">
<link data-trunk rel="sass" href="assets/css/style.sass">
<link data-trunk rel="scss" href="assets/css/style.scss">
</head>
</html>
组件中使用 css
重要:以下均为代码片段,请注意文件名,以及不同的样式表压入方法。
使用 &str
字符串字面量
如在 main.rs
中的应用入口组件上,使用 style.scss
声明的样式:
fn view(&self) -> Html {
type Anchor = RouterAnchor<Route>;
let home_cls = "nav";
html! {
<>
<div class="logo-title">
{ "tide-async-graphql-mongodb / frontend-yew" }
</div>
<div class=home_cls>
<Anchor route=Route::Users>
{ "用户列表" }
</Anchor>
{ " - " }
……
……
……
</>
}
}
如在 users.rs
中的用户列表组件上,使用 style.sass
声明的样式:
fn view(&self) -> Html {
html! {
<div class="users">
{ "用户列表 - 蓝色" }
</div>
}
}
使用 classes!
宏
yew 的近期版本中,新增了 classes!
宏,让样式表的压入更灵活,扩展性更强。
如在 home.rs
中的主界面组件上,使用 style.css
声明的样式:
fn view(&self) -> Html {
let home_cls = "home";
html! {
<div class=classes!(home_cls)>
{ "主界面 - 红色" }
</div>
}
}
如在 projects.rs
中的项目列表组件上,使用 style.scss
声明的样式:
fn view(&self) -> Html {
html! {
<div class=classes!("projects")>
{ "项目列表 - 绿色" }
</div>
}
}
引入图像
笔者向 assets
目录中放入一个 favicon.png
图像,向 assets/imgs
目录中放入一个 budshome.png
图像。
icon 和 <img>
都是通过 <link>
标签加入到构建路径,但 rel
属性则不同:icon 图像的引入,定义为 rel="icon"
,而 <img>
使用的图像资源,则要在构建时复制:可以选择复制单个文件,也可以复制文件夹。
<link data-trunk rel="icon" href="assets/favicon.png">
<link data-trunk rel="copy-dir" href="assets/imgs">
# 或者复制单个文件
<link data-trunk rel="copy-file" href="assets/imgs/budshome.png">
笔者使用的是复制文件夹。至此,index.html
文件完整内容为:
<!doctype html>
<html lang="zh">
<head>
<title>tide-async-graphql-mongodb - frontend - yew</title>
<meta charset="utf-8">
<link data-trunk rel="icon" href="assets/favicon.png">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="zzy, https://github.com/zzy/tide-async-graphql-mongodb">
<link data-trunk rel="css" href="assets/css/style.css">
<link data-trunk rel="sass" href="assets/css/style.sass">
<link data-trunk rel="scss" href="assets/css/style.scss">
<link data-trunk rel="copy-dir" href="assets/imgs">
</head>
</html>
在 yew 组件代码中,我们直接嵌入图像元素,注意此时图像路径从的根目录为 imgs
。
注意:
html!
宏中的标签必须闭合,即使 html5 标准中不需要/>
的自闭合标签,也不能省略/>
。
fn view(&self) -> Html {
type Anchor = RouterAnchor<Route>;
let home_cls = "nav";
html! {
<>
<div class="logo-title">
<img src="imgs/budshome.png" />
{ "tide-async-graphql-mongodb / frontend-yew" }
</div>
<div class=home_cls>
<Anchor route=Route::Users>
{ "用户列表" }
……
……
……
</>
}
}
运行和测试
执行 trunk serve
命令,浏览器会自动打开一个页面,或者手动在浏览器中访问 http://127.0.0.1:3001。如果你未按照上篇 trunk.toml
所介绍的配置,请访问你自定义的端口(默认为 8080)。
点击导航菜单,可以看到页面内容有了一些基础的样式,也显示了图像元素,当然还是很简陋。但本文是示例说明资源文件的引入和构建,目标已经达成。
代码重构:精简 html! 宏中代码,提取为函数
有朋友联系,讨论 main.rs
文件中的 <main>
标签内代码是否为好的实践?是否应当提取为一个函数之类的?以保持 html!
宏中代码尽量精简。
笔者深以为然,函数相对来说是较好的实践。同时引申一下:yew 的新版本,增加了 yew-functional
函数组件包,目前还未发布为独立的 crate。
我们简单对其重构,增加一个 switch
函数,返回值为 yew 中的 Html
类型,实质上是 VNode
枚举。
fn switch(switch: Route) -> Html {
match switch {
Route::Users => {
html! { <Users/> }
}
Route::Projects => {
html! { <Projects/> }
}
Route::Home => {
html! { <Home /> }
}
}
}
此时,main.rs
文件中的 <main>
标签内代码可精简为:
<main>
<Router<Route> render=Router::render(switch) />
</main>
谢谢您的阅读!