diff --git a/Cargo.toml b/Cargo.toml index e8381de..8605b41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ssrkit" -version = "0.1.1" +version = "0.1.2" edition = "2021" authors = ["Jerome Leong "] description = "SSR simplifier in Rust" @@ -22,4 +22,4 @@ nanoid = "0.4.0" indoc = "2.0.5" lru = "0.12.4" -ssrkit-macros = { version = "0.1.0", path = "../ssrkit-macros" } +ssrkit-macros = { version = "0.1.1" } diff --git a/README.md b/README.md index 51ea022..a46e362 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,20 @@ -# ssrkit +# SsrKit ![Crates.io](https://img.shields.io/crates/v/ssrkit) ![License](https://img.shields.io/crates/l/ssrkit) - `ssrkit`庫正在修改和完善islands功能中,暫時不要使用`features = ["island"]` - 避免後續的大改動。 +`SsrKit` 是一個用於簡化伺服器端渲染(SSR)使用的庫。本項目以 [ssr-rs](https://github.com/Valerioageno/ssr-rs)為核心,為其進一步擴展了功能和易用性。 -`ssrkit` 是一個強大且靈活的 Rust 函式庫,專為簡化伺服器端渲染(SSR)的實作流程而設計。它基於 [ssr-rs](https://github.com/Valerioageno/ssr-rs) 項目,進一步擴展了功能和易用性。 ssrkit 提供了一套完整的工具,包括參數處理系統、Island 架構支援和模板渲染功能,可無縫整合到各種 Web 框架中。 +## 什麼是 `ssr-rs`? +`ssr-rs` 用嵌入版本的v8引擎為JS 前端和 Rust 後端提供服務端渲染的橋樑。 +你可以使用 Vue、React等JavaScript 框架完成前端,然後通過`ssr-rs`的v8 將內容轉譯給Rust 進行後續處理。 +`ssr-rs`只提供最基本的功能,為了獲得更好的SSR 開發體驗,`SsrKit`就因此出現 -## 特性 - -- **輕量級和高效**: 最佳化的效能,最小化運行時開銷 -- **靈活的參數處理**: 自訂路由參數處理邏輯 -- **Island 架構**: 支援部分頁面的客戶端交互,提高應用程式的交互性 -- **範本渲染**: 內建模板系統,支援自訂內容插入 -- **易於整合**: 設計用於與各種 Rust Web 框架和前端框架無縫協作 -- **可擴展性**: 模組化設計,易於擴展和自訂 -- **執行緒安全性**: 支援多執行緒環境,適用於高並發場景 -- **型別安全**: 利用 Rust 的型別系統確保執行時間安全 +## SsrKit 的特性 +- 全局狀態管理:提供了基本的全局狀態管理功能,包括Session 管理和Cookie 管理 +- 基於路由的參數處理: 提供了基本的路由參數處理機制,如`/blog/[id]`中`[id]`參數的處理,並根據參數可以輸出不同的內容。 +- LRU的緩存機制:通過「最近最少使用」的緩存策略緩存常用的渲染結果,可以減少重複計算,從而加快響應速度。 +- Island 架構支持:(需要`island`feature)支持 Island 架構,這是一種用於實現局部更新和增量渲染的技術。通過 Island 架構,你可以將頁面拆分成多個獨立的 Island,每個 Island 可以獨立渲染和更新,從而提升用戶體驗。 ## 安裝 @@ -25,7 +22,7 @@ ```toml [dependencies] -ssrkit = { version = "0.1.1" , features = ["island"] } +ssrkit = { version = "0.1.2" , features = ["island"] } ssr = "0.5.8" # 確保使用與 ssrkit 相容的 ssr-rs 或其他 ssr 庫 ``` @@ -33,321 +30,87 @@ ssr = "0.5.8" # 確保使用與 ssrkit 相容的 ssr-rs 或其他 ssr 庫 ## 快速開始 -以下是一個基本的使用範例,展示了 ssrkit 的核心功能: +以下是一個快速開始的使用範例,簡單展示了 SsrKit 如何助力伺服器端渲染(SSR)。 ```rust -use serde_json::json; -use ssr::Ssr; use ssrkit::prelude::*; use std::collections::HashMap; +use std::num::NonZeroUsize; use std::sync::Arc; +use serde_json::{Map, Value}; +// 通過使用 #[params_handle] 宏來簡化 ParamsProcessor 的實現 #[params_handle] -fn BasicParamsProcessor(&self, path: &str, params: &HashMap) -> Map { +fn BlogParamsProcessor(path: &str, params: &HashMap) -> Map { + // 將請求參數轉換為 serde_json::Map params .iter() .map(|(k, v)| (k.clone(), Value::String(v.clone()))) .collect() } -// island 功能,需要使用了 island feature -struct ExampleIslandProcessor; -impl IslandProcessor for ExampleIslandProcessor { - fn process(&self, island_manager: &Arc, 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) - } +// (需要`island`feature) 使用 #[island_handle] 宏來簡化 IslandProcessor 的實現 +#[island_handle] +fn IslandDemoProcessor(&self, island_manager: &Arc, context: &ProcessContext) -> Value { + // 返回一個空的 JSON 對象 + serde_json::json!({}) } -fn main() { - // 初始化 SSR 元件 - init_ssr( - || Box::new(CombinedParamsProcessor::new().add("/", BasicParamsProcessor)), - Template::new, - None, // 可選的 SsrkitConfig - // 當使用了 island feature 才需要填寫 - || { - 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#"
- - {} -
"#, - instance_id, - serde_json::to_string(props).unwrap(), - initial_count - )) - }); - - manager - .add_island("Counter", Some(json!({"initialCount": 0}))) - .unwrap(); - - manager - }, - ); - - 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#"

{}

-
- {}"#, - content, - rendered - ), - "css": ".counter { font-weight: bold; }", - "head": "" - }) - .to_string()) - }); - - match result { - Ok(html) => println!("渲染的 HTML:\n{}", html), - Err(e) => println!("渲染錯誤:{}", e), - } -} -``` - -## 核心概念 - -### 初始化 SSR - -初始化 SSR 是使用 ssrkit 的第一步,它設定了整個 SSR 系統的核心元件: - -```rust -init_ssr( - params_processor_init: impl FnOnce() -> Box, - template_init: impl FnOnce() -> Template, - config: Option<&SsrkitConfig>, - island_manager_init: impl FnOnce() -> IslandManager, -) -``` -沒有使用了 `island` feature 的話,只需要3個變量 -- `params_processor_init`: 初始化參數處理器 -- `template_init`: 初始化模板 -- `config`: 可選的 SSRKit 配置 - -- `island_manager_init`: 初始化 Island 管理器 - -### 參數處理 (Params Processing) - -參數處理允許你根據路由和請求參數自訂邏輯 - -SsrKit 提供了簡單方便的過程宏 - `#[params_handle]` 供用戶使用: -```rust -#[params_handle] -fn UserParamsProcessor( - &self, - _path: &str, - params: &HashMap, -) -> serde_json::Map { - 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 渲染函數 +fn render_counter(_id: &str, _props: &Value) -> Result { + Ok("
Counter Island
".to_string()) } -``` -沒有使用過程宏的例子: -```rust -struct UserParamsProcessor; -impl ParamsProcessor for UserParamsProcessor { - fn process( - &self, - _path: &str, - params: &HashMap, - ) -> serde_json::Map { - 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 - } +// 初始化 SSR 組件 +pub fn init_ssr_components() { + // 初始化 SSR 渲染器 + SsrInitializer::new().changer() + // 整合ParamsProcessor,完成初始化 + .params_processor_init(|| Box::new(CombinedParamsProcessor::new() + .add("/user", UserParamsProcessor) + .add("/blog", BlogParamsProcessor))) + // 需要`island`feature + .island_manager_init(|| IslandManager::new().register() + // 註冊一個名為 "Counter" 的 Island,並使用預設 Island佔位 + .add_id("Counter") + // 註冊一個名為 "Counter1" 的 Island,,並指定其渲染函數和預設pops + .add("Counter1", Box::new(render_counter), None); + .finish()) + .finish() + .init(); + + // 記錄 SSR 組件初始化完成 } -``` - -### Island 架構 - -Island 架構允許你在伺服器端渲染的頁面中嵌入可互動的客戶端組件: - -```rust -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#"
- - {} -
"#, - instance_id, - serde_json::to_string(props).unwrap(), - initial_count - )) -}); - -island_manager.add_island("Counter", Some(json!({"initialCount": 0}))).unwrap(); -``` - -### 模板渲染 - -ssrkit 使用內建的模板系統來組合最終的 HTML 輸出: - -```rust -let template = Template::new(); -``` - -模板系統會自動處理 HTML、CSS 和額外的頭部內容。 - -### SSR 渲染器 - -SSR 渲染器是 ssrkit 的核心,它協調參數處理、Island 渲染和模板填充: - -```rust -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": "

你好,世界!

", - "css": ".greeting { color: blue; }", - "head": "" - }).to_string()) - } -); -``` - -## 進階使用 -### 自訂參數處理器 - -對於更複雜的應用,你可以組合多個參數處理器: - -```rust -let params_processor = CombinedParamsProcessor::new() - .add("/user", UserParamsProcessor) - .add("/blog", BlogParamsProcessor); -``` - -### Island 處理器 - -Island 處理器允許你在渲染過程中動態處理 Island 組件: - -```rust -struct IslandDemoProcessor; - -impl IslandProcessor for IslandDemoProcessor { - fn process(&self, island_manager: &Arc, 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 框架的範例: +// 主函數,這應替換成Rust Web Framework +fn main() { + // 初始化 SSR 組件 + init_ssr_components(); -```rust -use salvo::prelude::*; -use ssrkit::prelude::*; -use ssr::Ssr; + // 獲取渲染器 + let renderer = get_renderer(); -#[handler] -pub async fn handle_ssr(req: &mut Request, res: &mut Response) { - let path = req.uri().path().to_string(); - let params: std::collections::HashMap = req.params().iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect(); + // 定義一個簡單的渲染函數 + let render_fn: Box Result> = Box::new(|props| { + let json_props = serde_json::from_str::(props).map_err(|e| e.to_string())?; + let content = format!("Blog content with props: {}", json_props); + let result = serde_json::json!({ + "html": content, + "css": "", + "head": "", + "body": "" + }); + Ok(result.to_string()) + }); - 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!("

你好,Salvo!

{}", rendered), - "css": ".greeting { color: green; }", - "head": "" - }).to_string()) - } - ); + // 渲染頁面 + let path = "/blog/1"; + let params = HashMap::from([("id".to_string(), "1".to_string())]); + let result = renderer.render(path, params, render_fn); match result { - Ok(html) => { - res.render(Text::Html(html)); - }, - Err(e) => { - res.status_code(StatusCode::INTERNAL_SERVER_ERROR); - res.render(Text::Plain(format!("內部伺服器錯誤:{}", e))); - } + Ok((html, _cookies)) => println!("Rendered HTML: {}", html), + Err(e) => eprintln!("Render error: {}", e), } } ``` @@ -402,8 +165,7 @@ export function render(props) { 在 Rust 後端中,你可以這樣使用前端的 SSR 渲染: ```rust -let result = renderer.process_and_render( - &island_processor, +let result = renderer.render( &path, params, |props| { @@ -416,101 +178,13 @@ let result = renderer.process_and_render( Ok(rendered.to_string()) } + ,&CombinedIslandProcessor, // 如開啟了`island`才需要 ); ``` -## 效能考慮 - -- **選擇性水合**: Island 架構允許選擇性地水合頁面的特定部分,減少客戶端 JavaScript 的大小和執行時間。 -- **串流 SSR**: 雖然 ssrkit 本身不直接提供串流 SSR,但可以與支援串流輸出的 Web 框架結合使用,提高首次內容繪製(FCP)時間。 - -### 效能最佳化技巧 - -1. **快取策略優化**: - - 使用粗粒度的時間戳記來增加快取命中率。 - - 實作自訂快取鍵產生策略,排除頻繁變化的資料。 - -2. **減少動態內容**: - - 將頁面分為靜態部分和動態部分,靜態部分可以長期快取。 - - 使用客戶端 JavaScript 更新動態內容,例如時間戳記。 - -3. **使用 ETags**: - - 實作 ETags 來允許客戶端快取頁面,只有當內容真正變化時才發送新的回應。 - -4. **增加快取時間**: - - 如果內容不需要即時更新,可以增加快取的有效期限。 - -5. **批量處理**: - - 在 `IslandProcessor` 中實作批次處理邏輯,減少資料庫查詢次數。 - -6. **優化前端程式碼**: - - 確保前端 SSR 程式碼高效,避免不必要的計算和渲染。 - -## 與 ssr-rs 的關係 - -ssrkit 基於 ssr-rs 項目,並對其進行了擴展。以下是 ssrkit 與 ssr-rs 的主要區別和改進: - -1. **參數處理系統**: - - ssrkit 引入了更靈活的參數處理系統,允許根據路由自訂參數處理邏輯。 - -2. **Island 架構**: - - ssrkit 新增了 Isl​​and 架構支持,實現了更細粒度的客戶端互動。 - -3. **模板系統**: - - ssrkit 提供了內建的模板系統,簡化了 HTML、CSS 和頭部內容的組合。 - -4. **狀態管理**: - - ssrkit 引入了全域狀態管理,便於在不同元件間共享資料。 - -5. **擴充性**: - - ssrkit 設計了更多的擴充點,便於與其他函式庫和框架整合。 - -6. **型別安全**: - - ssrkit 更多地利用了 Rust 的類型系統,提供了更強的類型安全保證。 - -雖然 ssrkit 增加了這些特性,但它仍然依賴 ssr-rs 作為底層 SSR 引擎。在使用 ssrkit 時,你需要同時引入 ssr-rs 作為依賴。 - -## 最佳實踐 - -1. **元件化設計**: 將應用程式分解為小型、可重複使用的元件,以便於維護和測試。 - -2. **提前準備資料**: 在呼叫 SSR 渲染之前,盡可能準備好所需的所有資料。 - -3. **錯誤處理**: 實現全面的錯誤處理策略,確保在 SSR 失敗時有適當的回退機制。 - -4. **效能監控**: 使用效能監控工具追蹤 SSR 的執行時間和資源使用情況。 - -5. **程式碼分割**: 利用動態導入和懶載入技術減少初始載入時間。 - -6. **SSR 與 CSR 結合**: 對於不需要 SEO 的頁面部分,請考慮使用客戶端渲染。 - -7. **合理使用 Islands**: 只對真正需要互動的元件使用 Island 架構。 - -## 常見問題解答 - -1. **Q: ssrkit 可以與哪些 Web 框架一起使用? ** - A: ssrkit 設計為框架無關的,可以與大多數 Rust Web 框架(如 Actix, Rocket, Warp 等)整合。 - -2. **Q: 如何處理 SEO 問題? ** - A: 使用 ssrkit 的伺服器端渲染可以確保搜尋引擎能夠爬取到完整的頁面內容。確保在範本中包含必要的 meta 標籤。 - -3. **Q: ssrkit 支援增量靜態再生(ISR)嗎? ** - A: 目前 ssrkit 主要專注於動態 SSR。 ISR 可能會在未來版本中考慮支援。 - -4. **Q: 如何處理大型應用的效能問題? ** - A: 利用 ssrkit 的快取機制、考慮在 `IslandProcessor` 中實作並行處理,並使用 Island 架構來最小化客戶端 JavaScript。 - -5. **Q: ssrkit 如何處理前端路由? ** - A: ssrkit 透過與前端框架的整合來處理路由。在伺服器端,你可以根據 URL 選擇適當的元件進行渲染。 - -6. **Q: 如何自訂 Island 的客戶行為? ** - A: Island 的客戶行為應在前端框架中實現。 ssrkit 負責伺服器端渲染和初始狀態的傳遞。 - -7. **Q: ssrkit 是否支援部分頁面更新? ** - A: ssrkit 主要關注完整頁面的 SSR。部分頁面更新通常應由客戶端 JavaScript 處理。 - -8. **Q: 如何處理認證和授權? ** - A: 認證和授權邏輯應在 `ParamsProcessor` 或你的 Web 框架中實作。 ssrkit 可以根據這些邏輯的結果來渲染對應的內容。 +## 後續工作 +- 盡力在9月中前添加數個example +- 預期追加`i18n` feature ## 貢獻 @@ -526,9 +200,7 @@ ssrkit 使用 MIT 許可證。 ## 相關項目 -- [ssr-rs](https://github.com/Valerioageno/ssr-rs): ssrkit 的基礎 SSR 引擎 -- [salvo](https://github.com/salvo-rs/salvo): 一個相容於 ssrkit 的 Rust Web 框架 -- [Svelte](https://svelte.dev/): 一個受歡迎的前端框架,可以與 ssrkit 搭配使用 +- [ssr-rs](https://github.com/Valerioageno/ssr-rs): ssrkit 的基礎 SSR 引擎s ## 更新日誌 diff --git a/src/config.rs b/src/config.rs index 9b2877e..1434597 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,8 +17,8 @@ impl SsrkitConfig { Self::default() } - pub fn builder() -> SsrkitConfigBuilder { - SsrkitConfigBuilder::new() + pub fn change() -> SsrkitConfigChanger { + SsrkitConfigChanger::new() } pub fn get_nanoid_length(&self) -> usize { @@ -91,7 +91,7 @@ impl Clone for SsrkitConfig { } } -pub struct SsrkitConfigBuilder { +pub struct SsrkitConfigChanger { nanoid_length: Option, nanoid_alphabet: Option>, global_state_session_duration: Option, @@ -101,7 +101,7 @@ pub struct SsrkitConfigBuilder { island_cache_size: Option, } -impl SsrkitConfigBuilder { +impl SsrkitConfigChanger { pub fn new() -> Self { Self { nanoid_length: None, @@ -158,7 +158,7 @@ impl SsrkitConfigBuilder { } } -impl Default for SsrkitConfigBuilder { +impl Default for SsrkitConfigChanger { fn default() -> Self { Self::new() } diff --git a/src/init.rs b/src/init.rs new file mode 100644 index 0000000..cfc2e0a --- /dev/null +++ b/src/init.rs @@ -0,0 +1,133 @@ +use crate::cache::Cache; +use crate::config::{get_global_config, set_global_config, SsrkitConfig}; +use crate::params::ParamsProcessor; +use crate::state::init_global_state; +use crate::template::{init_template_cache, Template}; +use crate::{CombinedParamsProcessor, SsrRenderer}; +use std::sync::Arc; +use std::sync::OnceLock; + +#[cfg(feature = "island")] +use crate::island::{init_island_cache, IslandManager}; +#[cfg(feature = "island")] +use regex::Regex; + +pub static RENDERER: OnceLock = OnceLock::new(); +static TEMPLATE: OnceLock> = OnceLock::new(); +// Global static variables +#[cfg(feature = "island")] +pub static ISLAND_REGEX: OnceLock = OnceLock::new(); +#[cfg(feature = "island")] +static ISLAND_MANAGER: OnceLock> = OnceLock::new(); + +pub struct SsrInitializer { + params_processor_init: Box Box>, + template_init: Box Template>, + config: Option, + #[cfg(feature = "island")] + island_manager_init: Box IslandManager>, +} + +impl Default for SsrInitializer { + fn default() -> Self { + Self::new() + } +} +impl SsrInitializer { + pub fn new() -> Self { + Self { + params_processor_init: Box::new(|| Box::new(CombinedParamsProcessor::new())), + template_init: Box::new(Template::new), + config: Some(SsrkitConfig::default()), + #[cfg(feature = "island")] + island_manager_init: Box::new(IslandManager::new), + } + } + + pub fn changer() -> SsrInitializerChanger { + SsrInitializerChanger { + initializer: Self::new(), + } + } + + pub fn init(self) { + let config = self.config.unwrap_or_else(|| get_global_config().clone()); + + // 設置全局配置 + set_global_config(config.clone()); + + // 初始化全局配置 + crate::cache::init_cache(&config); + + // 初始化 GlobalState + let cache = Cache::new(|config| config.get_global_state_cache_size()); + let session_duration = config.get_global_state_session_duration(); + init_global_state(cache, config.clone(), session_duration); + + #[cfg(feature = "island")] + // 初始化正則表達式 + ISLAND_REGEX.get_or_init(|| { + Regex::new(r#"
"#).unwrap() + }); + + #[cfg(feature = "island")] + { + // 初始化 IslandManager + let island_manager = (self.island_manager_init)(); + init_island_cache(); + ISLAND_MANAGER.get_or_init(|| Arc::new(island_manager)); + } + + // 初始化 Template + let template = (self.template_init)(); + init_template_cache(); + TEMPLATE.get_or_init(|| Arc::new(template)); + + // 初始化 Renderer + RENDERER.get_or_init(|| { + SsrRenderer::new( + (self.params_processor_init)(), + #[cfg(feature = "island")] + ISLAND_MANAGER.get().unwrap().clone(), + TEMPLATE.get().unwrap().clone(), + ) + }); + } +} + +pub struct SsrInitializerChanger { + initializer: SsrInitializer, +} + +impl SsrInitializerChanger { + pub fn params_processor_init( + mut self, + params_processor_init: impl FnOnce() -> Box + 'static, + ) -> Self { + self.initializer.params_processor_init = Box::new(params_processor_init); + self + } + + pub fn template_init(mut self, template_init: impl FnOnce() -> Template + 'static) -> Self { + self.initializer.template_init = Box::new(template_init); + self + } + + pub fn config(mut self, config: SsrkitConfig) -> Self { + self.initializer.config = Some(config); + self + } + + #[cfg(feature = "island")] + pub fn island_manager_init( + mut self, + island_manager_init: impl FnOnce() -> IslandManager + 'static, + ) -> Self { + self.initializer.island_manager_init = Box::new(island_manager_init); + self + } + + pub fn finish(self) -> SsrInitializer { + self.initializer + } +} diff --git a/src/island.rs b/src/island.rs index aefb777..c87af76 100644 --- a/src/island.rs +++ b/src/island.rs @@ -6,7 +6,8 @@ use std::borrow::Cow; use std::collections::HashMap; use std::sync::{Arc, Mutex, OnceLock}; -pub type IslandRenderer = dyn Fn(&str, &Value) -> Result + Send + Sync; +pub type IslandRenderer = + dyn for<'a> Fn(&'a str, &'a Value) -> Result + Send + Sync; static ISLAND_CACHE: OnceLock> = OnceLock::new(); @@ -18,7 +19,7 @@ pub struct Island { pub struct IslandManager { islands: Arc, Island>>>, - renderers: Arc, Box>>>, + renderers: Arc, Arc>>>, config: Arc, } @@ -34,7 +35,7 @@ impl IslandManager { Self { islands: Arc::new(Mutex::new(HashMap::new())), renderers: Arc::new(Mutex::new(HashMap::new())), - config: config.into(), + config: Arc::new(config), } } @@ -163,12 +164,19 @@ impl CombinedIslandProcessor { } } + #[allow(clippy::should_implement_trait)] pub fn add(mut self, processor: P) -> Self { self.processors.push(Box::new(processor)); self } } +impl Default for CombinedIslandProcessor { + fn default() -> Self { + Self::new() + } +} + impl IslandProcessor for CombinedIslandProcessor { fn process(&self, island_manager: &Arc, context: &ProcessContext) -> Value { let mut result = serde_json::Map::new(); @@ -201,23 +209,26 @@ impl<'a> IslandRegistration<'a> { .renderers .lock() .unwrap() - .insert(id.clone(), Box::new(default_renderer)); + .insert(id.clone(), Arc::new(default_renderer)); let _ = self.manager.add_island(id, None); self } - pub fn add( + pub fn add( self, id: impl Into>, - renderer: Box, + renderer: F, default_props: Option, - ) -> Self { + ) -> Self + where + F: for<'b> Fn(&'b str, &'b Value) -> Result + Send + Sync + 'static, + { let id = id.into(); self.manager .renderers .lock() .unwrap() - .insert(id.clone(), renderer); + .insert(id.clone(), Arc::new(renderer)); let _ = self.manager.add_island(id, default_props); self } diff --git a/src/lib.rs b/src/lib.rs index 61483bb..146deb5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub mod island; pub mod cache; pub mod config; +pub mod init; pub mod params; pub mod render; pub mod state; @@ -10,12 +11,19 @@ pub mod template; // Re-export main types and traits #[cfg(feature = "island")] -pub use island::{get_or_render_island, IslandManager, IslandProcessor, CombinedIslandProcessor, ProcessContext}; +pub use island::{ + get_or_render_island, CombinedIslandProcessor, IslandManager, IslandProcessor, ProcessContext, +}; pub use cache::{init_cache, Cache}; -pub use config::SsrkitConfig; +pub use config::{get_global_config, set_global_config, SsrkitConfig}; +pub use init::SsrInitializer; pub use params::{CombinedParamsProcessor, ParamsProcessor}; -pub use render::{get_renderer, init_ssr, SsrRenderer}; +pub use render::{get_renderer, SsrRenderer}; +pub use state::{ + get_global_state, init_global_state, Cookie, CookieManager, GlobalState, Session, + SessionManager, +}; pub use template::Template; #[cfg(feature = "island")] @@ -28,12 +36,20 @@ pub use serde_json::{Map, Value}; // Prelude module for convenient imports pub mod prelude { #[cfg(feature = "island")] - pub use crate::island::{get_or_render_island, IslandManager, IslandProcessor, CombinedIslandProcessor, ProcessContext}; + pub use crate::island::{ + get_or_render_island, CombinedIslandProcessor, IslandManager, IslandProcessor, + ProcessContext, + }; pub use crate::cache::{init_cache, Cache}; - pub use crate::config::SsrkitConfig; + pub use crate::config::{get_global_config, set_global_config, SsrkitConfig}; + pub use crate::init::SsrInitializer; pub use crate::params::{CombinedParamsProcessor, ParamsProcessor}; - pub use crate::render::{get_renderer, init_ssr, SsrRenderer}; + pub use crate::render::{get_renderer, SsrRenderer}; + pub use crate::state::{ + get_global_state, init_global_state, Cookie, CookieManager, GlobalState, Session, + SessionManager, + }; pub use crate::template::Template; #[cfg(feature = "island")] diff --git a/src/render.rs b/src/render.rs index 3e8658e..1bac57c 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,23 +1,15 @@ -use crate::config::{get_global_config, SsrkitConfig}; +use crate::init::RENDERER; use crate::params::ParamsProcessor; -use crate::state::{get_global_state, init_global_state}; -use crate::template::{init_template_cache, Template}; -use crate::Cache; -use regex::Regex; +use crate::state::get_global_state; +use crate::template::Template; use serde_json::{json, Value}; use std::collections::HashMap; use std::sync::Arc; -use std::sync::OnceLock; #[cfg(feature = "island")] -use crate::island::{init_island_cache, IslandManager, IslandProcessor, ProcessContext}; - -// Global static variables -static ISLAND_REGEX: OnceLock = OnceLock::new(); -static RENDERER: OnceLock = OnceLock::new(); +use crate::init::ISLAND_REGEX; #[cfg(feature = "island")] -static ISLAND_MANAGER: OnceLock> = OnceLock::new(); -static TEMPLATE: OnceLock> = OnceLock::new(); +use crate::island::{IslandManager, IslandProcessor, ProcessContext}; pub struct SsrRenderer { params_processor: Box, @@ -27,7 +19,7 @@ pub struct SsrRenderer { } impl SsrRenderer { - fn new( + pub fn new( params_processor: Box, #[cfg(feature = "island")] island_manager: Arc, template: Arc