GLORY 指南
Web 应用。
Glory 浏览器应用是由 wasm-bindgen 启动的 wasm32 Rust 库。宿主入口找到空的 web_sys::Element,挂载共享组件树,保留 WebApp 守卫,并通过 HTTP 提供生成的 JavaScript 和 WASM。
选择 wasm32 Web 特性
DOM 应用需启用 Glory 的 web 特性,它选择 HTML 映射器和浏览器宿主;共享 Counter 本身仍只需要 glory::prelude::*。添加 wasm-bindgen 以导出启动函数,并为挂载查找添加带 Window、Document 和 Element 的 web-sys。编译后使用的 wasm-bindgen CLI 必须与锁定的 Rust crate 版本一致。
Cargo.toml
[package]
name = "my_glory_app"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
glory = { version = "0.3.1", default-features = false, features = ["web"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = "=0.2.129"
web-sys = { version = "=0.3.106", features = ["Window", "Document", "Element"] }cdylib 输出是浏览器加载的 WASM 库;rlib 使代码也可用于原生测试和服务端渲染。使用 --target wasm32-unknown-unknown 构建浏览器入口。旧的 web-csr 和 web-ssr 特性名不属于当前 API,应选择 web、ssr,以及按需选择 hydrate。
找到根元素并调用 launch
launch(root, widget) 将已构造的 Widget 挂载到 web_sys::Element。下面的完整入口使用自定义组件指南中的 Counter。将 Glory 错误转换为 JsValue,使模块启动失败时能报告有效信息,避免静默留下空白页面。
src/web.rs
use glory::prelude::*;
use std::cell::RefCell;
use wasm_bindgen::prelude::*;
thread_local! {
static APP: RefCell<Option<glory::web::WebApp>> = const { RefCell::new(None) };
}
#[wasm_bindgen(start)]
pub fn start() -> Result<(), JsValue> {
let root = web_sys::window()
.and_then(|window| window.document())
.and_then(|document| document.get_element_by_id("app"))
.ok_or_else(|| JsValue::from_str("missing #app mount element"))?;
let app = glory::web::launch(root, crate::Counter)
.map_err(|error| JsValue::from_str(&error.to_string()))?;
APP.with(|slot| *slot.borrow_mut() = Some(app));
Ok(())
}WebApp 是生命周期守卫
不要让返回的 WebApp 在 start 结束时离开作用域。释放它会释放 DOM、观察器、原生监听器、动画帧和所属异步任务。应将其保存在应用状态或 thread_local 守卫中,持续到界面不再需要运行。
使用 launch_with 构建根作用域状态
launch_with(root, factory) 接受 FnOnce(&Scope) -> W。构造根组件时也需要在根作用域分配 Cage 或其他状态,可使用它。工厂返回组件,不自行调用 show_in。组件值已经存在时,launch(root, widget) 是便捷的等价形式。
launch_with.rs
let app = glory::web::launch_with(root, |scope| {
let count = scope.cage(0_u64);
col().fill((
text().content(count.map(|count| format!("Count: {count}"))),
button()
.label("Add one")
.on(activate, move |_| count.revise(|count| *count += 1)),
))
})?;
APP.with(|slot| *slot.borrow_mut() = Some(app));两者均返回 Result<WebApp>。工厂形式不是重复执行的渲染回调:它与 Widget::build 一样只在挂载时运行一次。此时安装的响应式值会在启动后通过绑定更新保留式语义树。
CSR 挂载元素必须为空
launch 和 launch_with 需要空且未被占用的根元素。若元素有任何子节点或已存在 data-gly-app 属性,就会拒绝。文本节点也算子节点,因此 CSR 挂载元素内不能包含空白、注释、回退内容或服务端标记。该检查可防止客户端渲染静默删除本应水合的内容。
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Glory app</title>
</head>
<body>
<main id="app"></main>
<script type="module">
import init from "./my_glory_app.js";
await init();
</script>
</body>
</html>模块导入 wasm-bindgen 生成的 JavaScript 并等待初始化。随后 #[wasm_bindgen(start)] 函数运行,找到 #app 并挂载 Glory。Rust 启动逻辑只需查找根元素,无需自行创建 DOM;语义 Web 渲染器会创建和更新应用 DOM。
构建绑定并通过 HTTP 提供文件
将库编译为 WASM,以 --target web 运行 wasm-bindgen,并将 index.html 放在生成的 .js 和 _bg.wasm 文件旁。随后为输出目录启动 HTTP 服务器,在浏览器中打开显示的 localhost 地址。
terminal
rustup target add wasm32-unknown-unknown
cargo build --lib --target wasm32-unknown-unknown
# Use a wasm-bindgen-cli version matching the locked wasm-bindgen crate.
wasm-bindgen \
--target web \
--out-dir dist \
target/wasm32-unknown-unknown/debug/my_glory_app.wasm
# Copy index.html into dist, then serve the directory over HTTP.
python -m http.server 8080 --bind 127.0.0.1 --directory dist不要通过 file:// 打开 index.html
ES 模块和 WASM 加载需要正确的 HTTP 源及 MIME 处理;浏览器 file:// 安全规则通常会阻止这些请求。本地开发也应通过 HTTP 提供生成目录。原生 cargo build 成功并不能验证浏览器启动路径。
通过 hydrate 连接 SSR 和浏览器
纯客户端 launch 创建新的 DOM。水合是独立的显式操作,它接管版本化 SSR Snapshot,不重放初始属性设置,也不替换现有子树。原生服务器构建启用 ssr,wasm32 浏览器构建启用 hydrate;hydrate 隐含 web。若两个目标共用一个依赖声明,features = ["web", "ssr", "hydrate"] 可选择完整配置。
将共享组件工厂放在两个目标都编译的代码中。服务器和浏览器通过相同应用逻辑构造等价的初始语义树。水合不会自动传递任意 Rust 堆状态;初始值必须一致,异步请求数据需要显式资源或应用数据策略。
src/app.rs
// Shared by the native SSR binary and the wasm32 browser crate.
use glory::prelude::*;
pub fn app() -> impl Widget {
crate::Counter
}序列化 Snapshot
glory::ssr::snapshot(app()) 返回的 Snapshot,其 html 包含 hydrate 所需的版本标记。将片段原样插入应用独占的 #app 元素。还需以 data-gly-base="v1" 属性包含 glory::html::BASE_CSS;浏览器宿主便能识别该基础样式,避免重复添加。
src/server.rs
fn render_page() -> Result<String> {
// snapshot(), rather than render(), includes hydration version markers.
let snapshot = glory::ssr::snapshot(app())?;
Ok(format!(
"<!doctype html><html lang=\"en\"><head>\
<meta charset=\"utf-8\"><title>Counter</title>\
<style data-gly-base=\"v1\">{}</style></head><body>\
<main id=\"app\">{}</main>\
<script type=\"module\">import init from '/my_glory_app.js'; await init();</script>\
</body></html>",
glory::html::BASE_CSS,
snapshot.html,
))
}只需要 HTML 输出时,可使用 glory::ssr::render;但其未版本化字符串不满足水合约定。Snapshot 结构、组件工厂和浏览器特性必须一致。水合在附加运行时元数据前验证完整初始子树;版本或结构不匹配时报告错误,不会原地修复任意 DOM。
接管 DOM 并保留同一守卫
hydrate(root, widget) 和 hydrate_with(root, factory) 只用于 SSR 根元素。不要先对该根调用 launch:launch 要求为空,hydrate 则要求版本化服务器片段。浏览器仍会构造组件来建立状态和绑定,但会接管匹配的 DOM,不会重新创建。符合条件的已有用户输入会通过接管规则保留。
src/web.rs
// Enable Glory's hydrate feature for this browser build.
let app = glory::web::hydrate(root, app())
.map_err(|error| JsValue::from_str(&error.to_string()))?;
// Hydration also returns the WebApp guard that must be retained.
APP.with(|slot| *slot.borrow_mut() = Some(app));hydrate_with 工厂与 launch_with 一样接受 FnOnce(&Scope) -> W,适合共享工厂同时返回组件和浏览器应用状态所需句柄的场景。无论使用哪个入口,都要保留返回的 WebApp。水合改变初始 DOM 的获取方式,不改变运行应用的所有权。
回顾快速入门与宿主特性 →完整启动约定
以 web 特性为 wasm32 构建;查找 web_sys 根元素;只对空 CSR 挂载元素使用 launch 或 launch_with;通过 HTTP 提供模块和 WASM;保留 WebApp;接管服务端 DOM 时,选择 hydrate 或 hydrate_with,并使用匹配的 SSR Snapshot。