Yew 0.22 发布解读:借助 `LocalServerRenderer` 让服务端渲染登陆 WASI 单线程环境
2026/9/19 6:31:40 网站建设 项目流程

Yew 0.22 发布解读:借助LocalServerRenderer让服务端渲染登陆 WASI 单线程环境

【免费下载链接】yewRust / Wasm framework for creating reliable and efficient web applications项目地址: https://gitcode.com/gh_mirrors/ye/yew

Yew 0.22 版本的核心技术亮点,是把 Yew 的服务端渲染(Server-side Rendering,SSR)能力从仅限原生多线程目标,扩展到 WebAssembly System Interface(WASI)单线程环境。本篇文章以官方发布说明为主体,深入解读这一能力背后新增的LocalServerRendererAPI、它的单线程设计动机,并结合仓库内wasi_ssr_module示例与server_renderer.rs源码,给出可直接复制运行的构建与验证路径。

为什么 WASI 上的 SSR 需要一次专门的设计

在 Yew 0.22 之前,服务端渲染只能在原生(native)目标上运行。旧版 SSR 实现在异步上下文里直接创建新的任务(基于 tokio 的运行时),这要求一个多线程环境才能工作,而 WASI 目标并不满足这一前提。因此在新版本中,Yew 增加了一个专门面向单线程环境的渲染函数,用来支撑单线程场景下的渲染需求。

从仓库中的平台抽象层可以看到背后的设计思路:packages/yew/src/platform.rs 说明 Yew 维护了一套在 JavaScript Runtime 与 Native Runtime 之间兼容的运行层(该模块以 tokise 名义发布到 crates.io)。它实现了一个可以执行?SendSend!Send)futures 的单线程运行时:在支持多线程的平台上,Yew 会按 CPU 核心数派生多个独立运行时,并通过spawn_local保证任务停留在原线程;而在 WebAssembly 目标上,所有任务都被调度到主线程。正是这一层"单线程也能跑异步任务"的能力,为 WASI 上的 SSR 提供了地基。

从 CHANGELOG 可以看到该能力的正式落点:Add WASI support for server-side rendering(CHANGELOG.md),由 @langyo 通过 PR #3534 合入。

新增 API:LocalServerRenderer

与既有的ServerRenderer相对应,Yew 0.22 新增了LocalServerRenderer,它在当前线程上完成渲染,不会自行派生运行时。其源码位于 packages/yew/src/server_renderer.rs,文档注释明确列出它的适用前提:

  • wasm-bindgen-futures被选作 Yew 运行时的后端;
  • 运行在 Yew 的Runtime之内;
  • 或运行在 tokio 的LocalSet之内。

也就是说,调用方(例如tokio::main(flavor = "current_thread"))需要提供单线程异步上下文,而LocalServerRenderer本身不再引入额外的多线程调度。

API 一览

方法说明
LocalServerRenderer::<COMP>::new()使用组件的Default属性创建渲染器
LocalServerRenderer::<COMP>::with_props(props)使用自定义属性创建渲染器
.hydratable(bool)设置渲染结果是否可水合(hydration),默认true
.render().await渲染应用并返回String
.render_to_string(&mut w).await将渲染结果追加写入已有String
.render_stream()渲染为一个Stream<Item = String>,可流式输出

hydratabletrue时,产物中会包含额外的水合辅助信息;官方文档建议客户端侧(Renderer::hydrate)用于水合的 Virtual DOM 布局必须与 SSR 输出严格一致。

ServerRenderer的分工

LocalServerRenderer是渲染的"内层":它直接调用Scope::<COMP>::new(None)创建组件作用域,并通过render_into_stream将虚拟 DOM 渲染进字符串缓冲(见 packages/yew/src/html/component/scope.rs)。

而原有的ServerRenderer变成了"外层"封装:它会通过spawn_rendering_task把任务投递到指定的 YewRuntime(或当前线程的LocalHandle、默认Runtime),随后内部其实就是委托给LocalServerRenderer::with_props(...).hydratable(...).render()来完成渲染。换句话说,LocalServerRenderer正是整个 SSR 渲染管线在单线程环境下的核心实现。

实战:用wasi_ssr_module示例跑通 WASI SSR

仓库提供了官方示例 examples/wasi_ssr_module,它演示了在 WASI 目标上运行一个带路由(yew-router)的 SSR 应用。

组件与路由定义

路由定义在 examples/wasi_ssr_module/src/router.rs,包含首页/、带参数的/t/{id}以及#[not_found]兜底路由:

#[derive(Routable, PartialEq, Eq, Clone, Debug)] pub enum Route { #[at("/")] Portal, #[at("/t/{id}")] Thread { id: String }, #[not_found] #[at("/404")] NotFound, } pub fn switch(routes: Route) -> Html { html! { match routes { Route::Portal => <h1>{"Hello"}</h1>, Route::Thread { id } => <h1>{format!("Thread id {}", id)}</h1>, Route::NotFound => <h1>{"Not found"}</h1>, } } }

应用入口使用MemoryHistory构造路由历史(AnyHistory::from(MemoryHistory::new())push("/")),再通过<Router history={history}>包住内容组件——这一模式与客户端路由一致,区别只是历史状态保存在内存中而非浏览器地址栏。

渲染函数与单线程 main

核心渲染逻辑在 examples/wasi_ssr_module/src/main.rs:

pub async fn render() -> Result<String> { let renderer = LocalServerRenderer::<App>::new(); let html_raw = renderer.render().await; let mut body = String::new(); body.push_str("<body>"); body.push_str("<div id='app' style='width: 100vw; height: 100vh; position: fixed;'>"); body.push_str(&html_raw); body.push_str("</div>"); body.push_str("</body>"); Ok(body) } #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { let ret = render().await?; println!("{ret}"); Ok(()) }

值得注意的要点:

  • LocalServerRenderer只渲染组件树本身,不负责完整 HTML 文档骨架,因此示例在服务端手动拼接<body>与挂载节点<div id='app'>,这正是官方 SSR 文档中"head 标签必须由服务器在 Yew 之外生成并拼入模板"这一规则的直接体现;
  • #[tokio::main(flavor = "current_thread")]提供单线程异步执行环境,与LocalServerRenderer不派生自有运行时的约束吻合;
  • 示例的依赖清单(examples/wasi_ssr_module/Cargo.toml)中,yew开启了ssrfeature(features = ["ssr"]),这是 SSR 相关 API 可用的前提。

构建与运行

按示例 README(examples/wasi_ssr_module/README.md)的说明,在仓库根目录执行:

cargo build --manifest-path examples/wasi_ssr_module/Cargo.toml --target wasm32-wasip1 --release

随后用 wasmtime CLI(依赖其 WASI preview2 支持)运行:

wasmtime target/wasm32-wasip1/release/wasi_ssr_module.wasm

提示:如果 wasmtime 报出__wbindgen_placeholder__::__wbindgen_xxx之类的导入错误,可尝试执行cargo update解决依赖版本问题。

SSR 相关组件生命周期与数据获取注意事项

在 WASI 上跑 SSR 时,以下官方文档(website/docs/advanced-topics/server-side-rendering.mdx)中的约定同样适用:

  • 推荐使用函数组件:除use_effect/use_effect_with之外的所有 hooks 在组件首次成功渲染为Html前都能正常工作;效果(effects)在 SSR 期间不会执行;
  • Web API 不可用web_sys等浏览器 API 在服务端渲染时会 panic,应把依赖 Web API 的逻辑隔离到use_effect中;
  • 数据获取:官方推荐用<Suspense />在服务端等待组件不再挂起后再序列化输出,客户端水合时<Suspense />内的元素保持脱水状态直到子组件全部就绪;低层数据钩子use_prepared_state!use_transitive_state!可将服务端计算结果(经bincode+base64编码为<script>标签)传递给客户端;
  • 水合一致性:客户端必须产生与 SSR 完全一致的 Virtual DOM 布局,否则水合可能失败;非规范 HTML 也可能被浏览器改写结构导致水合失败。

simple_ssr示例(examples/simple_ssr/src/lib.rs)展示了"SSR 期间用use_prepared_state!异步拉取数据 +<Suspense>提供 fallback"的完整组合,可作为与 WASI 场景相互印证的参考实现。

总结

Yew 0.22 通过引入面向单线程环境的LocalServerRenderer,让 Yew 的 SSR 能力从原生多线程目标扩展到 WASI 环境:LocalServerRenderer承担渲染核心,ServerRenderer则负责多线程场景下的任务派发,二者在同一渲染管线上各司其职。配合wasm32-wasip1目标、current_thread异步运行时与 wasmtime 运行器,开发者可以在不依赖线程池的前提下输出可水合的 HTML,为 WASI 服务端场景下的 Yew 应用提供了完整的 SSR 路径。

【免费下载链接】yewRust / Wasm framework for creating reliable and efficient web applications项目地址: https://gitcode.com/gh_mirrors/ye/yew

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询