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


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

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

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

    Yew官方给出的例子如下:

    #[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>
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    因此,我就顺着这个例子给出的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中的这段配置了。
    [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"] }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这个配置,说明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

    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"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

        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");
                    }
                })
            })
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

  • 相关阅读:
    【机器学习】李宏毅——自监督式学习
    Android数据库处理重复插入Insert数据的问题
    【Linux】多线程
    高通camera-sensor分辨率简单梳理
    [Python]黑色背景白色块滑动视频
    计算机毕业设计Java家电仓储管理系统(源码+系统+mysql数据库+lw文档)
    Coursera自动驾驶1.4——车辆建模
    尚医通 (十七) --------- 数据字典开发
    拆分整数为2的幂次项和 → 理解多重背包问题二进制优化的核心思想
    选择结构——分段函数练习题2
  • 原文地址:https://blog.csdn.net/firefox1/article/details/132910180