From 4370b027e3c228478da3a2099dd2c21798af907f Mon Sep 17 00:00:00 2001 From: Jerome Date: Wed, 31 Jul 2024 00:45:39 +0800 Subject: [PATCH] docs(README): update comments and examples to use Chinese language for better localization and understanding chore(README): remove unnecessary example code to streamline documentation and improve clarity docs(README): update performance optimization section and enhance future expansion ideas to provide clearer guidance for users feat(cache): implement a generic LRU cache with configurable size based on SsrkitConfig to improve performance and memory management feat(config): create SsrkitConfig struct to hold cache size configurations for islands and templates refactor(island): integrate Cache into IslandManager for efficient island rendering and caching chore(lib): update module exports to include new cache and config modules for better organization chore(render): adjust rendering logic to utilize the new cache system for improved performance and maintainability refactor(template.rs): replace LruCache with a generic Cache type for improved flexibility and maintainability feat(template.rs): add init_template_cache function to initialize the cache with a configurable size feat(template.rs): implement render_template function to simplify template rendering with caching support --- README.md | 139 +++++++++++++++++++++++++++--------------------- src/cache.rs | 56 +++++++++++++++++++ src/config.rs | 16 ++++++ src/island.rs | 74 ++++++++------------------ src/lib.rs | 19 ++++--- src/render.rs | 33 ++++++------ src/template.rs | 107 +++++++++++++++++++++---------------- 7 files changed, 260 insertions(+), 184 deletions(-) create mode 100644 src/cache.rs create mode 100644 src/config.rs diff --git a/README.md b/README.md index accdd6e..ae0bd6f 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ impl IslandProcessor for ExampleIslandProcessor { } fn main() { - // Initialize SSR components + // 初始化 SSR 組件 init_ssr( || Box::new(CombinedParamsProcessor::new().add("/", BasicParamsProcessor)), || { @@ -75,7 +75,7 @@ fn main() { let instance_id = props["instanceId"].as_str().unwrap_or(""); Ok(format!( r#"
- + {}
"#, instance_id, @@ -89,24 +89,25 @@ fn main() { manager }, Template::new, + None, // 可選的 SsrkitConfig ); let renderer = get_renderer(); - // Simulate request + // 模擬請求 let path = "/example"; let mut params = HashMap::new(); params.insert("user".to_string(), "Alice".to_string()); - // Execute rendering + // 執行渲染 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("Guest"); - let content = format!("Welcome, {}! Here's an interactive counter:", user); + let user = parsed_props["params"]["user"].as_str().unwrap_or("訪客"); + let content = format!("歡迎,{}!這是一個互動計數器:", user); Ok(json!({ "html": format!( @@ -115,14 +116,14 @@ fn main() { content ), "css": ".counter { font-weight: bold; }", - "head": "" + "head": "" }).to_string()) } ); match result { - Ok(html) => println!("Rendered HTML:\n{}", html), - Err(e) => println!("Rendering error: {}", e), + Ok(html) => println!("渲染的 HTML:\n{}", html), + Err(e) => println!("渲染錯誤:{}", e), } } ``` @@ -138,26 +139,14 @@ init_ssr( params_processor_init: impl FnOnce() -> Box, island_manager_init: impl FnOnce() -> IslandManager, template_init: impl FnOnce() -> Template, + config: Option<&SsrkitConfig>, ) ``` - `params_processor_init`: 初始化參數處理器 - `island_manager_init`: 初始化 Island 管理器 - `template_init`: 初始化模板 - -例如: - -```rust -init_ssr( - || Box::new(CombinedParamsProcessor::new().add("/", BasicParamsProcessor)), - || { - let manager = IslandManager::new(); - // 註冊 Islands... - manager - }, - Template::new, -); -``` +- `config`: 可選的 SSRKit 配置 ### 參數處理 (Params Processing) @@ -187,7 +176,7 @@ island_manager.register("Counter", |_, props| { let instance_id = props["instanceId"].as_str().unwrap_or(""); Ok(format!( r#"
- + {}
"#, instance_id, @@ -223,9 +212,9 @@ let result = renderer.process_and_render( |props| { // 實現渲染邏輯 Ok(json!({ - "html": "

Hello, World!

", + "html": "

你好,世界!

", "css": ".greeting { color: blue; }", - "head": "" + "head": "" }).to_string()) } ); @@ -262,9 +251,6 @@ impl IslandProcessor for IslandDemoProcessor { Value::Object(islands) } } - -let island_processor = CombinedIslandProcessor::new() - .add(IslandDemoProcessor); ``` ### 與 Web 框架集成 @@ -283,8 +269,7 @@ pub async fn handle_ssr(req: &mut Request, res: &mut Response) { .collect(); let renderer = get_renderer(); - let island_processor = CombinedIslandProcessor::new() - .add(IslandDemoProcessor); + let island_processor = IslandDemoProcessor; let result = renderer.process_and_render( &island_processor, @@ -293,9 +278,9 @@ pub async fn handle_ssr(req: &mut Request, res: &mut Response) { |props| { // 實現渲染邏輯 Ok(json!({ - "html": "

Hello, Salvo!

", + "html": "

你好,Salvo!

", "css": ".greeting { color: green; }", - "head": "" + "head": "" }).to_string()) } ); @@ -306,7 +291,7 @@ pub async fn handle_ssr(req: &mut Request, res: &mut Response) { }, Err(e) => { res.status_code(StatusCode::INTERNAL_SERVER_ERROR); - res.render(Text::Plain(format!("Internal Server Error: {}", e))); + res.render(Text::Plain(format!("內部伺服器錯誤:{}", e))); } } } @@ -314,9 +299,9 @@ pub async fn handle_ssr(req: &mut Request, res: &mut Response) { ## 前端集成 -ssrkit 需要前端框架的配合才能實現完整的服務器端渲染。以下是一個使用 Svelte 的示例: +ssrkit 需要前端框架的配合才能實現完整的伺服器端渲染。以下是一個使用 Svelte 的示例: -### 前端 SSR 入口點 (例如 `ssr.js`) +### 前端 SSR 入口點(例如 `ssr.js`) ```javascript import App from './App.svelte'; @@ -367,7 +352,7 @@ let result = renderer.process_and_render( &path, params, |props| { - // 呼叫前端的 SSR 函數 + // 調用前端的 SSR 函數 let ssr_result = call_frontend_ssr(props)?; // 解析前端返回的 JSON @@ -378,17 +363,7 @@ let result = renderer.process_and_render( ); ``` -這裡的 `call_frontend_ssr` 函數需要根據你的專案設置來實現。它可能涉及調用 Node.js 進程或使用 WebAssembly 來執行 JavaScript 代碼。 - -### 構建過程 - -為了使這個流程工作,你需要: - -1. 將你的 Svelte 應用編譯成可以在服務器端運行的 JavaScript。 -2. 確保 `ssr.js` 文件可以被你的 Rust 後端訪問和執行。 -3. 實現一個方法來從 Rust 調用 JavaScript 的 `render` 函數。 - -這可能需要使用工具如 Webpack 或 Rollup 來打包你的前端代碼,並使用 Node.js 集成或 WebAssembly 來在 Rust 中執行 JavaScript。 +這裡的 `call_frontend_ssr` 函數需要根據你的專案設置來實現。它可能涉及調用 Node.js 進程或使用 WebAssembly 來執行 JavaScript 程式碼。 ## 效能考慮 @@ -398,26 +373,68 @@ ssrkit 在設計時考慮了效能: - **並行處理**: 雖然目前 ssrkit 不直接支持非同步渲染,但你可以在 `IslandProcessor` 的實現中使用並行處理技術來提高效能。 - **選擇性水合**: Island 架構允許選擇性地水合頁面的特定部分,減少客戶端 JavaScript 的大小和執行時間。 -## 未來可能的擴展 +### 效能優化技巧 + +1. **快取策略優化**: + - 使用粗粒度的時間戳來增加快取命中率。 + - 實現自定義快取鍵生成策略,排除頻繁變化的數據。 -- **非同步渲染支持**: 未來版本可能會考慮添加非同步處理機制,以支持更複雜的渲染場景。 -- **串流渲染**: 考慮實現串流渲染支持,以提高大型頁面的響應速度。 -- **更細粒度的快取控制**: 提供更多選項來控制和自定義快取行為。 +2. **減少動態內容**: + - 將頁面分為靜態部分和動態部分,靜態部分可以長期快取。 + - 使用客戶端 JavaScript 更新動態內容,如時間戳。 + +3. **使用 ETags**: + - 實現 ETags 來允許客戶端快取頁面,只有當內容真正變化時才發送新的回應。 + +4. **增加快取時間**: + - 如果內容不需要即時更新,可以增加快取的有效期。 + +5. **批量處理**: + - 在 `IslandProcessor` 中實現批量處理邏 ## 與 ssr-rs 的比較 ssrkit 基於 ssr-rs 專案,但進行了以下改進和擴展: -1. **更完善的參數處理系統**:ssrkit 提供了 `ParamsProcessor` 和 `CombinedParamsProcessor`,支持更靈活的路由特定參數處理。 -2. **Island 架構**:ssrkit 引入了 Island 架構,支持部分頁面的客戶端互動,提高了應用的互動性。 -3. **模板系統**:ssrkit 提供了更靈活的模板系統,支持自定義模板和預設模板。 -4. **Island 管理器**:ssrkit 引入了 `IslandManager`,用於管理和渲染 Island 組件。 -5. **前端框架集成**:ssrkit 提供了與前端框架(如 React、Svelte)更好的集成支持。 -6. **多執行緒支持**:ssrkit 設計上考慮了多執行緒環境,適用於高併發場景。 -7. **擴展性**:ssrkit 的模組化設計使得擴展和自定義功能更加容易。 -8. **類型安全**:ssrkit 充分利用 Rust 的類型系統,提供了更好的類型安全保證。 +1. **更完善的參數處理系統**: + - ssrkit 提供了 `ParamsProcessor` 和 `CombinedParamsProcessor`,支持更靈活的路由特定參數處理。 + - 允許開發者根據不同路由實現自定義的參數處理邏輯。 + +2. **Island 架構**: + - ssrkit 引入了 Island 架構,支持部分頁面的客戶端互動,提高了應用的互動性。 + - 允許在伺服器端渲染的頁面中嵌入可互動的客戶端組件。 + +3. **模板系統**: + - ssrkit 提供了更靈活的模板系統,支持自定義模板和預設模板。 + - 允許更細緻的控制over HTML結構、CSS和JavaScript注入。 + +4. **Island 管理器**: + - ssrkit 引入了 `IslandManager`,用於管理和渲染 Island 組件。 + - 提供了一個集中的方式來註冊、管理和渲染 Island 組件。 + +5. **前端框架集成**: + - ssrkit 提供了與前端框架(如 React、Svelte)更好的集成支持。 + - 簡化了前端和後端之間的數據傳遞和狀態管理。 + +6. **多執行緒支持**: + - ssrkit 設計上考慮了多執行緒環境,適用於高併發場景。 + - 提供了執行緒安全的數據結構和操作。 + +7. **擴展性**: + - ssrkit 的模組化設計使得擴展和自定義功能更加容易。 + - 提供了更多的鉤子(hooks)和擴展點。 + +8. **類型安全**: + - ssrkit 充分利用 Rust 的類型系統,提供了更好的類型安全保證。 + - 減少了運行時錯誤,提高了代碼的可靠性。 + +9. **效能優化**: + - ssrkit 提供了更多的內建效能優化選項。 + - 包括更細緻的快取控制和選擇性水合等特性。 -總的來說,ssrkit 在 ssr-rs 的基礎上,提供了更豐富的功能和更好的開發體驗,特別適合構建大型和複雜的 SSR 應用。 +10. **開發者體驗**: + - ssrkit 提供了更豐富的文檔和示例。 + - 設計了更直觀和易用的 API,減少了學習曲線。 ## 常見問題解答 diff --git a/src/cache.rs b/src/cache.rs new file mode 100644 index 0000000..8baca14 --- /dev/null +++ b/src/cache.rs @@ -0,0 +1,56 @@ +use crate::config::SsrkitConfig; +use lru::LruCache; +use std::num::NonZeroUsize; +use std::sync::{Mutex, OnceLock}; + +static CONFIG: OnceLock = OnceLock::new(); + +pub struct Cache { + cache: OnceLock>>, + cache_size_fn: Box NonZeroUsize + Send + Sync>, +} + +impl Cache { + pub fn new( + cache_size_fn: impl Fn(&SsrkitConfig) -> NonZeroUsize + Send + Sync + 'static, + ) -> Self { + Self { + cache: OnceLock::new(), + cache_size_fn: Box::new(cache_size_fn), + } + } + + fn get_or_create_cache(&self) -> &Mutex> { + self.cache.get_or_init(|| { + let config = CONFIG.get().cloned().unwrap_or_else(SsrkitConfig::default); + Mutex::new(LruCache::new((self.cache_size_fn)(&config))) + }) + } + + pub fn insert(&self, key: &str, value: T) -> T { + let mut cache_guard = self.get_or_create_cache().lock().unwrap(); + cache_guard.put(key.to_string(), value.clone()); + value + } + + pub fn get(&self, key: &str) -> Option { + let mut cache_guard = self.get_or_create_cache().lock().unwrap(); + cache_guard.get(key).cloned() + } + + pub fn get_or_insert(&self, key: &str, create_fn: F) -> T + where + F: FnOnce() -> T, + { + if let Some(value) = self.get(key) { + value + } else { + let new_value = create_fn(); + self.insert(key, new_value) + } + } +} + +pub fn init_cache(config: &SsrkitConfig) { + let _ = CONFIG.set(config.clone()); +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..ce01547 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,16 @@ +use std::num::NonZeroUsize; + +#[derive(Clone)] +pub struct SsrkitConfig { + pub island_cache_size: NonZeroUsize, + pub template_cache_size: NonZeroUsize, +} + +impl Default for SsrkitConfig { + fn default() -> Self { + Self { + island_cache_size: NonZeroUsize::new(100).unwrap(), + template_cache_size: NonZeroUsize::new(100).unwrap(), + } + } +} diff --git a/src/island.rs b/src/island.rs index d25ca75..5e92ed9 100644 --- a/src/island.rs +++ b/src/island.rs @@ -1,5 +1,4 @@ -use crate::config::SsrkitConfig; -use lru::LruCache; +use crate::Cache; use nanoid::nanoid; use serde_json::Value; use std::borrow::Cow; @@ -8,6 +7,8 @@ use std::sync::{Arc, Mutex, OnceLock}; pub type IslandRenderer = dyn Fn(&str, &Value) -> Result + Send + Sync; +static ISLAND_CACHE: OnceLock> = OnceLock::new(); + pub struct Island { pub id: Cow<'static, str>, pub version: u64, @@ -18,6 +19,12 @@ pub struct IslandManifest { islands: HashMap, Island>, } +impl Default for IslandManifest { + fn default() -> Self { + Self::new() + } +} + impl IslandManifest { pub fn new() -> Self { Self { @@ -70,41 +77,17 @@ pub trait IslandProcessor: Send + Sync { fn process(&self, island_manager: &Arc, context: &ProcessContext) -> Value; } -pub struct CombinedIslandProcessor { - processors: Vec>, -} - -impl CombinedIslandProcessor { - pub fn new() -> Self { - Self { - processors: Vec::new(), - } - } - - pub fn add(mut self, processor: P) -> Self { - self.processors.push(Box::new(processor)); - self - } -} - -impl IslandProcessor for CombinedIslandProcessor { - fn process(&self, island_manager: &Arc, context: &ProcessContext) -> Value { - let mut result = serde_json::Map::new(); - for processor in &self.processors { - let processed = processor.process(island_manager, context); - if let Value::Object(map) = processed { - result.extend(map); - } - } - Value::Object(result) - } -} - pub struct IslandManager { manifest: Arc>, renderers: Arc, Box>>>, } +impl Default for IslandManager { + fn default() -> Self { + Self::new() + } +} + impl IslandManager { pub fn new() -> Self { Self { @@ -184,29 +167,16 @@ impl Clone for IslandManager { } } -static ISLAND_CACHE: OnceLock>> = OnceLock::new(); -static CONFIG: OnceLock = OnceLock::new(); - -pub fn init_island_cache(config: &SsrkitConfig) { - let _ = CONFIG.set(config.clone()); +pub fn init_island_cache() { + ISLAND_CACHE.get_or_init(|| Cache::new(|config| config.island_cache_size)); } pub fn get_or_render_island(key: &str, render_fn: F) -> String where F: FnOnce() -> String, { - let cache = ISLAND_CACHE.get_or_init(|| { - let binding = SsrkitConfig::default(); - let config = CONFIG.get().unwrap_or(&binding); - Mutex::new(LruCache::new(config.island_cache_size)) - }); - let mut cache_guard = cache.lock().unwrap(); - match cache_guard.get(key) { - Some(cached) => cached.clone(), - None => { - let rendered = render_fn(); - cache_guard.put(key.to_string(), rendered.clone()); - rendered - } - } -} \ No newline at end of file + ISLAND_CACHE + .get() + .expect("Island cache not initialized") + .get_or_insert(key, render_fn) +} diff --git a/src/lib.rs b/src/lib.rs index 8bcacf0..3a4b904 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,25 +1,28 @@ +pub mod cache; +pub mod config; pub mod island; pub mod params; pub mod render; pub mod template; -pub mod config; // Re-export main types and traits -pub use island::{CombinedIslandProcessor, IslandManager, IslandProcessor, ProcessContext, get_or_render_island}; +pub use cache::{init_cache, Cache}; +pub use config::SsrkitConfig; +pub use island::{get_or_render_island, IslandManager, IslandProcessor, ProcessContext}; pub use params::{CombinedParamsProcessor, ParamsProcessor}; -pub use render::{init_ssr, SsrRenderer, get_renderer}; +pub use render::{get_renderer, init_ssr, SsrRenderer}; pub use template::Template; -pub use config::SsrkitConfig; // Re-export important types from serde_json that are commonly used pub use serde_json::{Map, Value}; // Prelude module for convenient imports pub mod prelude { - pub use crate::island::{CombinedIslandProcessor, IslandManager, IslandProcessor, ProcessContext, get_or_render_island}; + pub use crate::cache::{init_cache, Cache}; + pub use crate::config::SsrkitConfig; + pub use crate::island::{get_or_render_island, IslandManager, IslandProcessor, ProcessContext}; pub use crate::params::{CombinedParamsProcessor, ParamsProcessor}; - pub use crate::render::{init_ssr, SsrRenderer, get_renderer}; + pub use crate::render::{get_renderer, init_ssr, SsrRenderer}; pub use crate::template::Template; - pub use crate::config::SsrkitConfig; pub use serde_json::{Map, Value}; -} \ No newline at end of file +} diff --git a/src/render.rs b/src/render.rs index bb71528..651ef86 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,7 +1,7 @@ use crate::island::{init_island_cache, IslandManager, IslandProcessor, ProcessContext}; use crate::params::ParamsProcessor; use crate::template::{init_template_cache, Template}; -use crate::SsrkitConfig; +use crate::{cache, SsrkitConfig}; use regex::Regex; use serde_json::{json, Value}; use std::collections::HashMap; @@ -61,7 +61,6 @@ impl SsrRenderer { let (replaced_html, islands) = self.replace_island_placeholders(html)?; rendered["html"] = Value::String(replaced_html); used_islands = islands; - } else { } } @@ -74,8 +73,7 @@ impl SsrRenderer { serde_json::json!({}) }; - let result = self.template.render(&rendered, &islands_json); - result + self.template.render(&rendered, &islands_json) } fn replace_island_placeholders(&self, html: &str) -> Result<(String, Vec), String> { @@ -107,22 +105,23 @@ impl SsrRenderer { processor: &P, path: &str, params: HashMap, - render_fn: F + render_fn: F, ) -> Result where P: IslandProcessor, F: FnOnce(&str) -> Result, { - let context = ProcessContext { path: path.to_string() }; + let context = ProcessContext { + path: path.to_string(), + }; let islands_value = self.island_manager.process_islands(processor, &context); - + let content = self.render(path, params, render_fn)?; - + // 尝试解析 content 为 JSON,如果失败则将其作为字符串处理 - let content_value = serde_json::from_str::(&content).unwrap_or_else(|_| { - json!({ "html": content }) - }); - + let content_value = + serde_json::from_str::(&content).unwrap_or_else(|_| json!({ "html": content })); + self.template.render(&content_value, &islands_value) } } @@ -135,6 +134,9 @@ pub fn init_ssr( ) { let config = config.cloned().unwrap_or_default(); + // 初始化全局配置 + cache::init_cache(&config); + // 初始化正則表達式 ISLAND_REGEX.get_or_init(|| { Regex::new(r#"
"#).unwrap() @@ -142,12 +144,12 @@ pub fn init_ssr( // 初始化 IslandManager let island_manager = island_manager_init(); - init_island_cache(&config); + init_island_cache(); ISLAND_MANAGER.get_or_init(|| Arc::new(island_manager)); // 初始化 Template let template = template_init(); - init_template_cache(&config); + init_template_cache(); TEMPLATE.get_or_init(|| Arc::new(template)); // 初始化 Renderer @@ -158,9 +160,8 @@ pub fn init_ssr( TEMPLATE.get().unwrap().clone(), ) }); - } pub fn get_renderer() -> &'static SsrRenderer { RENDERER.get().expect("Renderer not initialized") -} \ No newline at end of file +} diff --git a/src/template.rs b/src/template.rs index 2a12b9f..86bdf3b 100644 --- a/src/template.rs +++ b/src/template.rs @@ -1,27 +1,18 @@ -use crate::config::SsrkitConfig; -use lru::LruCache; +use crate::Cache; use serde_json::Value; use std::collections::HashSet; use std::sync::OnceLock; -use std::sync::Mutex; -static TEMPLATE_CACHE: OnceLock>> = OnceLock::new(); -static CONFIG: OnceLock = OnceLock::new(); +static TEMPLATE_CACHE: OnceLock> = OnceLock::new(); -pub fn init_template_cache(config: &SsrkitConfig) { - let _ = CONFIG.set(config.clone()); -} +pub struct Template; -fn get_cache() -> &'static Mutex> { - TEMPLATE_CACHE.get_or_init(|| { - let binding = SsrkitConfig::default(); - let config = CONFIG.get().unwrap_or(&binding); - Mutex::new(LruCache::new(config.template_cache_size)) - }) +impl Default for Template { + fn default() -> Self { + Self::new() + } } -pub struct Template; - impl Template { pub fn new() -> Self { Self @@ -29,37 +20,43 @@ impl Template { pub fn render(&self, content: &Value, islands: &Value) -> Result { let cache_key = format!("{:?}:{:?}", content, islands); - + // Try to get from cache - if let Some(cached_html) = get_cache().lock().unwrap().get(&cache_key) { - return Ok(cached_html.clone()); + if let Some(cached_html) = TEMPLATE_CACHE.get().unwrap().get(&cache_key) { + return Ok(cached_html); } - let html = content["html"].as_str().ok_or("Missing 'html' in content")?; + let html = content["html"] + .as_str() + .ok_or("Missing 'html' in content")?; let css = content["css"].as_str().unwrap_or(""); let head_extra = content["head"].as_str().unwrap_or(""); let body_extra = content["body"].as_str().unwrap_or(""); let island_scripts = self.generate_island_scripts(islands); - let rendered_html = if html.trim().starts_with("") || html.trim().starts_with("") { - // If it's a complete HTML, we just need to insert extra content - let mut rendered_html = html.to_string(); - - // Insert extra head content before - if let Some(head_end) = rendered_html.find("") { - rendered_html.insert_str(head_end, &format!("{}{}", head_extra, css, island_scripts)); - } + let rendered_html = + if html.trim().starts_with("") || html.trim().starts_with("") { + // If it's a complete HTML, we just need to insert extra content + let mut rendered_html = html.to_string(); + + // Insert extra head content before + if let Some(head_end) = rendered_html.find("") { + rendered_html.insert_str( + head_end, + &format!("{}{}", head_extra, css, island_scripts), + ); + } - // Insert extra body content after - if let Some(body_start) = rendered_html.find("") { - rendered_html.insert_str(body_start + 6, body_extra); - } + // Insert extra body content after + if let Some(body_start) = rendered_html.find("") { + rendered_html.insert_str(body_start + 6, body_extra); + } - rendered_html - } else { - // If it's not a complete HTML, use our template - indoc::formatdoc! {r#" + rendered_html + } else { + // If it's not a complete HTML, use our template + indoc::formatdoc! {r#" @@ -73,13 +70,16 @@ impl Template { "#} - }; + }; let mut final_html = rendered_html; self.replace_island_placeholders(&mut final_html, islands); // Store result in cache - get_cache().lock().unwrap().put(cache_key, final_html.clone()); + TEMPLATE_CACHE + .get() + .unwrap() + .insert(&cache_key, final_html.clone()); Ok(final_html) } @@ -87,8 +87,11 @@ impl Template { fn replace_island_placeholders(&self, html: &mut String, islands: &Value) { if let Some(island_instances) = islands.as_object() { for (name, instance) in island_instances { - if let (Some(id), Some(island_html)) = (instance["id"].as_str(), instance["html"].as_str()) { - let placeholder = format!(r#"
"#, id, name); + if let (Some(id), Some(island_html)) = + (instance["id"].as_str(), instance["html"].as_str()) + { + let placeholder = + format!(r#"
"#, id, name); *html = html.replace(&placeholder, island_html); } } @@ -98,12 +101,8 @@ impl Template { fn generate_island_scripts(&self, islands: &Value) -> String { let unique_islands: HashSet<&str> = islands .as_object() - .map(|obj| { - obj.values() - .filter_map(|v| v["id"].as_str()) - .collect() - }) - .unwrap_or_else(HashSet::new); + .map(|obj| obj.values().filter_map(|v| v["id"].as_str()).collect()) + .unwrap_or_default(); unique_islands .into_iter() @@ -116,4 +115,18 @@ impl Template { .collect::>() .join("\n") } -} \ No newline at end of file +} + +pub fn init_template_cache() { + TEMPLATE_CACHE.get_or_init(|| Cache::new(|config| config.template_cache_size)); +} + +pub fn render_template(key: &str, render_fn: F) -> String +where + F: FnOnce() -> String, +{ + TEMPLATE_CACHE + .get() + .expect("Template cache not initialized") + .get_or_insert(key, render_fn) +}