1 个不稳定版本
0.1.0 | 2024 年 8 月 1 日 |
---|
#369 在 Web 编程
每月 110 次下载
50KB
893 行
ssrkit
ssrkit
是一个强大且灵活的 Rust 函数库,专门为简化服务器端渲染(SSR)的实现流程而设计。它基于 ssr-rs 项目,进一步扩展了功能和易用性。ssrkit 提供了一套完整的工具,包括参数处理系统、Island 架构支持和模板渲染功能,可以无缝集成到各种 Web 框架中。
特性
- 轻量级和高效: 最佳化的性能,最小化运行时开销
- 灵活的参数处理: 自定义路由参数处理逻辑
- Island 架构: 支持部分页面的客户端交互,提高应用程序的交互性
- 模板渲染: 内置模板系统,支持自定义内容插入
- 易于集成: 设计用于与各种 Rust Web 框架和前端框架无缝协作
- 可扩展性: 模块化设计,易于扩展和自定义
- 线程安全: 支持多线程环境,适用于高并发场景
- 类型安全: 利用 Rust 的类型系统确保运行时安全
安装
将以下内容添加到您的 Cargo.toml
文件中:
[dependencies]
ssrkit = { git = "https://github.com/jeromeleong/ssrkit.git" }
ssr = "0.5.7" # 確保使用與 ssrkit 相容的 ssr 版本
注意:ssrkit 依赖于 ssr-rs 和其他 SSR 相关函数库。请确保您的项目中包含了所有必要的依赖。
快速开始
以下是一个基本的使用示例,展示了 ssrkit 的核心功能:
use serde_json::json;
use ssr::Ssr;
use ssrkit::prelude::*;
use std::collections::HashMap;
use std::sync::Arc;
struct BasicParamsProcessor;
impl ParamsProcessor for BasicParamsProcessor {
fn process(
&self,
_path: &str,
params: &HashMap<String, String>,
) -> serde_json::Map<String, Value> {
params
.iter()
.map(|(k, v)| (k.clone(), Value::String(v.clone())))
.collect()
}
}
struct ExampleIslandProcessor;
impl IslandProcessor for ExampleIslandProcessor {
fn process(&self, island_manager: &Arc<IslandManager>, context: &ProcessContext) -> Value {
let mut islands = serde_json::Map::new();
if context.path == "/example" {
// Counter Island
let counter = get_or_render_island("counter", || {
island_manager
.render_island(
"Counter",
&json!({
"initialCount": 0,
"client": "load"
}),
)
.unwrap_or_default()
});
islands.insert(
"counter".to_string(),
json!({
"id": "Counter",
"html": counter
}),
);
}
Value::Object(islands)
}
}
fn main() {
// 初始化 SSR 元件
init_ssr(
|| Box::new(CombinedParamsProcessor::new().add("/", BasicParamsProcessor)),
|| {
let manager = IslandManager::new();
manager.register("Counter", |_, props| {
let initial_count = props["initialCount"].as_i64().unwrap_or(0);
let instance_id = props["instanceId"].as_str().unwrap_or("");
Ok(format!(
r#"<div id="{}" data-island="Counter" data-props='{}'>
<button>遞增</button>
<span>{}</span>
</div>"#,
instance_id,
serde_json::to_string(props).unwrap(),
initial_count
))
});
manager
.add_island("Counter", Some(json!({"initialCount": 0})))
.unwrap();
manager
},
Template::new,
None, // 可選的 SsrkitConfig
);
let renderer = get_renderer();
// 模擬請求
let path = "/example";
let mut params = HashMap::new();
params.insert("user".to_string(), "Alice".to_string());
// 執行渲染
let result = renderer.process_and_render(&ExampleIslandProcessor, path, params, |props| {
let parsed_props: Value = serde_json::from_str(props).unwrap();
let user = parsed_props["params"]["user"].as_str().unwrap_or("訪客");
let content = format!("歡迎,{}!這是一個互動計數器:", user);
// 使用 ssr-rs 進行實際的 SSR 渲染
let ssr = Ssr::new("path/to/your/frontend/bundle.js", "render").unwrap();
let rendered = ssr.render(props).unwrap();
Ok(json!({
"html": format!(
r#"<h1>{}</h1>
<div data-island="Counter" data-name="counter" data-props='{{"initialCount": 0}}'></div>
{}"#,
content,
rendered
),
"css": ".counter { font-weight: bold; }",
"head": "<meta name='description' content='SSRKit 範例與計數器'>"
})
.to_string())
});
match result {
Ok(html) => println!("渲染的 HTML:\n{}", html),
Err(e) => println!("渲染錯誤:{}", e),
}
}
核心概念
初始化 SSR
初始化 SSR 是使用 ssrkit 的第一步,它设定了整个 SSR 系统的核心组件:
init_ssr(
params_processor_init: impl FnOnce() -> Box<dyn ParamsProcessor>,
island_manager_init: impl FnOnce() -> IslandManager,
template_init: impl FnOnce() -> Template,
config: Option<&SsrkitConfig>,
)
params_processor_init
: 初始化参数处理器island_manager_init
: 初始化 Island 管理器template_init
: 初始化模板config
: 可选的 SSRKit 配置
参数处理 (Params Processing)
参数处理允许您根据路由和请求参数自定义逻辑:
struct UserParamsProcessor;
impl ParamsProcessor for UserParamsProcessor {
fn process(
&self,
_path: &str,
params: &HashMap<String, String>,
) -> serde_json::Map<String, serde_json::Value> {
let mut processed = serde_json::Map::new();
if let Some(user_id) = params.get("id") {
processed.insert("user_id".to_string(), user_id.clone().into());
processed.insert("is_admin".to_string(), (user_id == "admin").into());
}
processed
}
}
Island 架构
Island 架构允许您在服务器端渲染的页面中嵌入可交互的客户端组件:
island_manager.register("Counter", |_, props| {
let initial_count = props["initialCount"].as_i64().unwrap_or(0);
let instance_id = props["instanceId"].as_str().unwrap_or("");
Ok(format!(
r#"<div id="{}" data-island="Counter" data-props='{}'>
<button>遞增</button>
<span>{}</span>
</div>"#,
instance_id,
serde_json::to_string(props).unwrap(),
initial_count
))
});
island_manager.add_island("Counter", Some(json!({"initialCount": 0}))).unwrap();
模板渲染
ssrkit 使用内置的模板系统来组合最终的 HTML 输出:
let template = Template::new();
模板系统会自动处理 HTML、CSS 和额外的头部内容。
SSR 渲染器
SSR 渲染器是 ssrkit 的核心,它协调参数处理、Island 渲染和模板填充:
let renderer = get_renderer();
let result = renderer.process_and_render(
&island_processor,
path,
params,
|props| {
// 實作渲染邏輯,這裡通常會呼叫 ssr-rs 的 SSR 功能
// let ssr = Ssr::new("path/to/your/frontend/bundle.js", "render").unwrap();
// let rendered = ssr.render(props).unwrap();
//
Ok(json!({
"html": "<h1>你好,世界!</h1>",
"css": ".greeting { color: blue; }",
"head": "<meta name='description' content='我的 SSR 頁面'>"
}).to_string())
}
);
高级使用
自定义参数处理器
对于更复杂的应用,您可以组合多个参数处理器:
let params_processor = CombinedParamsProcessor::new()
.add("/user", UserParamsProcessor)
.add("/blog", BlogParamsProcessor);
Island 处理器
Island 处理器允许您在渲染过程中动态处理 Island 组件:
struct IslandDemoProcessor;
impl IslandProcessor for IslandDemoProcessor {
fn process(&self, island_manager: &Arc<IslandManager>, context: &ProcessContext) -> Value {
let mut islands = serde_json::Map::new();
if context.path == "/demo" {
let counter = get_or_render_island("counter", || {
island_manager.render_island("Counter", &json!({"initialCount": 5})).unwrap_or_default()
});
islands.insert("counter".to_string(), json!({"id": "Counter", "html": counter}));
}
Value::Object(islands)
}
}
与 Web 框架集成
以下是一个使用 Salvo 框架的示例:
use salvo::prelude::*;
use ssrkit::prelude::*;
use ssr::Ssr;
#[handler]
pub async fn handle_ssr(req: &mut Request, res: &mut Response) {
let path = req.uri().path().to_string();
let params: std::collections::HashMap<String, String> = req.params().iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
let renderer = get_renderer();
let island_processor = IslandDemoProcessor;
let result = renderer.process_and_render(
&island_processor,
&path,
params,
|props| {
// 使用 ssr-rs 進行實際的 SSR 渲染
let ssr = Ssr::new("path/to/your/frontend/bundle.js", "render").unwrap();
let rendered = ssr.render(props).unwrap();
Ok(json!({
"html": format!("<h1>你好,Salvo!</h1>{}", rendered),
"css": ".greeting { color: green; }",
"head": "<meta name='description' content='Salvo SSR 範例'>"
}).to_string())
}
);
match result {
Ok(html) => {
res.render(Text::Html(html));
},
Err(e) => {
res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
res.render(Text::Plain(format!("內部伺服器錯誤:{}", e)));
}
}
}
前端集成
ssrkit 需要前端框架的配合才能实现完整的服务器端渲染。以下是一个使用 Svelte 的示例:
前端 SSR 入口点(例如 ssr.js
)
import App from './App.svelte';
import * as routes from './routes';
export function render(props) {
const { url, params, islands } = JSON.parse(props);
let component = routes.NotFound;
let componentProps = { ...params };
// 根據 URL 選擇適當的元件
if (url === '/') {
component = routes.Index;
} else if (url === '/about') {
component = routes.About;
} else if (url.startsWith('/blog')) {
component = routes.BlogPost;
} else if (url.startsWith('/user')) {
component = routes.User;
} else if (url === '/island-demo') {
component = routes.IslandDemo;
componentProps.initialCount = 10; // 設定計數器的初始值
}
const rendered = App.render({
url,
component,
props: componentProps,
islands
});
return JSON.stringify({
html: rendered.html,
css: rendered.css.code,
head: rendered.head
});
}
在 ssrkit 中使用前端渲染
在 Rust 后端中,您可以使用以下方式使用前端 SSR 渲染:
let result = renderer.process_and_render(
&island_processor,
&path,
params,
|props| {
// 使用 ssr-rs 呼叫前端的 SSR 函數
let ssr = Ssr::new("path/to/your/frontend/bundle.js", "render").unwrap();
let ssr_result = ssr.render(props)?;
// 解析前端傳回的 JSON
let rendered: serde_json::Value = serde_json::from_str(&ssr_result)?;
Ok(rendered.to_string())
}
);
性能考虑
- 可选水合: Island 架构允许有选择性地水合页面的特定部分,减少客户端 JavaScript 的大小和执行时间。
- 串流 SSR: 虽然 ssrkit 本身不直接提供串流 SSR,但可以与支持串流输出的 Web 框架结合使用,提高首次内容绘制(FCP)时间。
性能优化技巧
- 缓存策略优化:
- 使用粗粒度的时间戳来增加缓存命中率。
- 实现自定义缓存键生成策略,排除频繁变化的数据。
- 减少动态内容:
- 将页面分为静态部分和动态部分,静态部分可以长期缓存。
- 使用客户端 JavaScript 更新动态内容,例如时间戳。
- 使用 ETags:
- 实现 ETags 来允许客户端缓存页面,只有当内容真正变化时才发送新的响应。
- 增加缓存时间:
- 如果内容不需要即时更新,可以增加缓存的有效期限。
- 批量处理:
- 在
IslandProcessor
中实现批量处理逻辑,减少数据库查询次数。
- 优化前端代码:
- 确保前端 SSR 代码高效,避免不必要的计算和渲染。
与 ssr-rs 的关系
ssrkit 基于 ssr-rs 项目,并对其进行了扩展。以下是 ssrkit 与 ssr-rs 的主要区别和改进:
- 参数处理系统:
- ssrkit 引入了更灵活的参数处理系统,允许根据路由自定义参数处理逻辑。
- Island 架构:
- ssrkit 新增了 Island 架构支持,实现了更细粒度的客户端交互。
- 模板系统:
- ssrkit 提供了内置的模板系统,简化了 HTML、CSS 和头部内容的组合。
- 状态管理:
- ssrkit 引入了全局状态管理,便于在不同组件间共享数据。
- 扩展性:
- ssrkit 设计了更多的扩展点,便于与其他函数库和框架整合。
- 类型安全:
- ssrkit 更多地利用了 Rust 的类型系统,提供了更强的类型安全保证。
尽管 ssrkit 增加了这些特性,但它仍然依赖于 ssr-rs 作为底层 SSR 引擎。在使用 ssrkit 时,您需要同时引入 ssr-rs 作为依赖。
最佳实践
-
组件化设计: 将应用程序分解为小型、可重复使用的组件,以便于维护和测试。
-
提前准备数据: 在调用 SSR 渲染之前,尽可能准备好所需的所有数据。
-
错误处理: 实现全面的错误处理策略,确保在 SSR 失败时有适当的回退机制。
-
性能监控: 使用性能监控工具跟踪 SSR 的执行时间和资源使用情况。
-
代码分割:利用动态导入和懒加载技术减少初始加载时间。
-
SSR 与 CSR 结合:对于不需要 SEO 的页面部分,请考虑使用客户端渲染。
-
合理使用 Islands:只对真正需要交互的组件使用 Island 架构。
常见问题解答
-
**Q: ssrkit 可以与哪些 Web 框架一起使用? ** A: ssrkit 设计为框架无关的,可以与大多数 Rust Web 框架(如 Actix, Rocket, Warp 等)整合。
-
**Q: 如何处理 SEO 问题? ** A: 使用 ssrkit 的服务器端渲染可以确保搜索引擎能够爬取到完整的页面内容。确保在模板中包含必要的 meta 标签。
-
**Q: ssrkit 支持增量静态再生(ISR)吗? ** A: 目前 ssrkit 主要专注于动态 SSR。ISR 可能会在未来版本中考虑支持。
-
**Q: 如何处理大型应用的性能问题? ** A: 利用 ssrkit 的缓存机制、考虑在
IslandProcessor
中实现并行处理,并使用 Island 架构来最小化客户端 JavaScript。 -
**Q: ssrkit 如何处理前端路由? ** A: ssrkit 通过与前端框架的整合来处理路由。在服务器端,你可以根据 URL 选择适当的组件进行渲染。
-
**Q: 如何自定义 Island 的客户端行为? ** A: Island 的客户端行为应在前端框架中实现。ssrkit 负责服务器端渲染和初始状态的传递。
-
**Q: ssrkit 是否支持部分页面更新? ** A: ssrkit 主要关注完整页面的 SSR。部分页面更新通常应由客户端 JavaScript 处理。
-
**Q: 如何处理认证和授权? ** A: 认证和授权逻辑应在
ParamsProcessor
或你的 Web 框架中实现。ssrkit 可以根据这些逻辑的结果来渲染对应的内容。
贡献
我们欢迎社区贡献!如果你有任何改进建议或发现 bug,请开启一个 issue 或提交一个 pull request。
许可
ssrkit 使用 MIT 许可证。
致谢
特别感谢 ssr-rs 项目的开发者,他的工作为 ssrkit 奠定了基础。
相关项目
更新日志
请查看 CHANGELOG.md 文件以了解最新的变更和版本信息。
联系我们
如果你有任何问题或建议,可以通过以下方式联系我:
- GitHub Issues: ssrkit issues
- Email: [email protected]
感谢你使用 ssrkit!我们期待看到你用它构建的出色应用。
依赖
~5.5–8MB
~136K SLoC