[2023.09.15]: Yew SSR模式下的条件编译问题

昨天才写了Rust的条件编译,没想到这个问题还没完。

昨天我还为它的强大而赞叹不已,自以为对它了解了八九成,然而今天我才猛然意识到,这个里面的深度远超我的想象。我估计,我现在只了解其中的冰山一角吧。

故事从客户端post数据的后端api说起。

习以为常的思维影响着我解决问题的方式,对于这种问题,我通常会寻找一个库来处理后端的 API 交互问题。因此,我在互联网上四处搜寻与 Yew SSR 开发相关的示例,并发现大部分示例都使用了 reqwest 库。但是,当时我并没有注意到一个细节,这些示例都只调用了 get 方法,却没有涉及到 post 方法的应用场景。

Yew官方给出的例子如下:

rust 复制代码
#[cfg(feature = "ssr")]
async fn fetch_uuid() -> Uuid {
    // reqwest works for both non-wasm and wasm targets.
    let resp = reqwest::get("https://httpbin.org/uuid").await.unwrap();
    let uuid_resp = resp.json::<UuidResponse>().await.unwrap();

    uuid_resp.uuid
}

#[function_component]
fn Content() -> HtmlResult {
    let uuid = use_prepared_state!((), async move |_| -> Uuid { fetch_uuid().await })?.unwrap();

    Ok(html! {
        <div>{"Random UUID: "}{uuid}</div>
    })
}

因此,我就顺着这个例子给出的reqwest,来解决我要post数据到后端api的问题。

让我们细看一下官方给出的例子代码,fetch_uuid带有条件编译#[cfg(feature="ssr")],也就是说这个函数只会在编译服务器端的target文件时才会编译进去,然后看一下调用fetch_uuid的地方,用的是use_prepared_state!宏,它也会作用于服务器端。连起来理解,就是在服务器端调用fetch_uuid函数来获取数据,并将数据"发送"到客户端。这里的"发送"实际上就是wasm的加载过程,也就是说在服务器端和客户端都可以看到这个值。理解了这段例子代码之后,我们会发现这里的过程都发生在服务器端,而不是客户端。我们要把数据post到后端api,这个场景发生在wasm加载到浏览器之后,由用户在UI上的操作发起的。

显然,这段例子代码没有涉及到我的需求,但是reqwest库中也有post的功能,能不能通过reqwest库来实现我要把前端的数据post到后端api的这个需求呢?

答案是NO。

首先聊一下SSR模式,即服务器端生成,在这个模式中,针对后端api的访问就有两种情况

  1. 在服务器的运行环境中来访问后端api;
  2. 在浏览器端的wasm环境中来访问后端api;
    明白了这两种情况后,我们就很好理解在Cargo.toml中的这段配置了。
toml 复制代码
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen-futures = "0.4"
wasm-logger = "0.2"
log = "0.4"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1.29.0", features = ["full"] }
warp = "0.3"
clap = { version = "4", features = ["derive"] }

这个配置,说明wasm-bindgen-futures, wasm-logger, log这3个依赖只有在编译成wasm时才会有效,而tokio, warp, clap这个3个依赖,只有在编译成服务端代码时才会有效。

而reqwest依赖于tokio库,因此reqwest只能用于服务器端的api处理,不能用于浏览器端wasm环境中的api处理。当然,你完全不用担心如果你在开发这类需求时,会不会在这个地方配置出错,完全不用担心这个问题,因为编译器会把问题报给你。这一点总算是给Rust挽回了一点面子。

故事到这里还没有完,彩蛋还在后面。

按照上面的Cargo.toml,我最初在运行cargo clippy时总是报错,说wasm_bindgen_futures::spawn_local不存在,但后来发现运行trunk build index.html时,这个错误又没了,所以我就怀疑上了条件编译这一块。说明在运行trunk build index.html时,它产生的目标是wasm。算了吧,能够完成build也不错,先把流程走通。但是后来再运行cargo run --features=ssr --bin=ssr_server -- --dir dist时,又报wasm_bindgen_futures::spawn_local不存在的错误。我想这里编译的是服务端代码,它的编译目标应该是非wasm的。而wasm-bindgen-futures又是声明在wasm下面,因此找不到wasm_bindgen_futures。所以,我尝试把wasm-bindgen-futures一到[dependencies]下,这下子都不报错了。

因此,我觉得这里应该折射出一个问题,即Yew的SSR开发模式中,没有严格区分哪些代码要被编译成服务器端的target,哪些代码要编译成客户端wasm的target。

当然,最后的结局编译成功,运行时也没有什么问题。

总结一下,在Yew的SSR开发中,虽然代码都在一个项目之内,但是要注意区分代码和功能是哪些运行在服务器环境,哪些运行在浏览器的wasm环境。可以通过指定特定目标架构的依赖项([target.'cfg()'.dependencies]),配合编译器来帮助我们发现错误。

说了这么多,最终的代码如下:

Cargo.toml

toml 复制代码
reqwest = { version = "0.11.18", features = ["json"] }
gloo-net = { version="0.4.0", features=["json", "http"]}
wasm-bindgen-futures = "0.4"

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-logger = "0.2"
log = "0.4"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1.29.0", features = ["full"] }
warp = "0.3"

客户端发起post请求的代码:

rust 复制代码
    let on_save = {
        Callback::from(move |editorData: EditorData| {
            spawn_local(async move {
                if let Ok(req) = Request::post("/notes").json(&editorData) {
                    let _ = req.send().await;
                } else {
                    log!("invalid json");
                }
            })
        })
    };

今天的故事就讲到这里,里面有不对的地方,还望大家多多指教。(终于要到后端的开发了)

相关推荐
乌暮5 小时前
# Spring 入门与 Spring MVC 实战指南:从零理解 IoC 到 RESTful 图书管理
java·开发语言·后端·学习·spring·mvc·restful
haluhalu.5 小时前
Agent = LLM + 上下文 + 工具(含观察 / 动作空间、ReAct 循环)
前端·javascript·人工智能·python·react.js·机器学习·个人开发
H.莓飛5 小时前
【C++】类与对象(末):static、友元与拷贝优化
linux·开发语言·c++·visual studio
青春逝如流水5 小时前
Java 线程在内存模型(JMM)与 JVM 实现:规范与落地的多元路径
java·开发语言·jvm·多线程·ai文章
weixin_4604435610 小时前
2026年新修订《网络安全法》实施:Java私有化考试系统如何做好SBOM、漏洞治理与安全升级?
java·开发语言·在线考试系统·企业培训考试系统·宏远培训考试系统·国企培训考试系统·国产化培训系统
子兮曰11 小时前
把 649 种文件格式塞进一个网页里:一个「文件不出浏览器」的预览器
前端·javascript·后端
Cc.Y13 小时前
Java零基础入门:可变字符串与包装类:StringBuilder、StringBuffer 与 通讯录管理系统实战
java·开发语言·python
sm_9267870514 小时前
RFID 标签打印的技术实现要点与二次开发实践
java·大数据·前端·c++·编辑器
阿森_dev14 小时前
2027 届毕设选题怎么选:30 个 Java 方向选题的实现难度排序
java·开发语言·spring boot·毕业设计·课程设计
by2099914 小时前
C++ 多态:同一个基类接口,调用不同的函数
开发语言·c++·经验分享·多态