kotoba_server/frontend/
render_ir.rs

1//! レンダリングエンジンIR定義
2//!
3//! 仮想DOM、コンポーネントツリー、レンダリングパイプラインを表現します。
4
5use kotoba_core::prelude::*;
6use crate::frontend::component_ir::{ComponentIR, ElementIR, ElementChild, ComponentType, ExecutionEnvironment};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10/// 仮想DOMノードIR
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub enum VirtualNodeIR {
13    Element(ElementIR),
14    Component(ComponentIR),
15    Text(String),
16    Fragment(Vec<VirtualNodeIR>),
17}
18
19impl VirtualNodeIR {
20    pub fn element(tag_name: String) -> Self {
21        VirtualNodeIR::Element(ElementIR::new(tag_name))
22    }
23
24    pub fn component(component: ComponentIR) -> Self {
25        VirtualNodeIR::Component(component)
26    }
27
28    pub fn text(content: String) -> Self {
29        VirtualNodeIR::Text(content)
30    }
31
32    pub fn fragment(children: Vec<VirtualNodeIR>) -> Self {
33        VirtualNodeIR::Fragment(children)
34    }
35}
36
37/// レンダリングコンテキスト
38#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
39pub struct RenderContext {
40    pub environment: ExecutionEnvironment,
41    pub route_params: Properties,
42    pub query_params: Properties,
43    pub global_state: Properties,
44    pub is_server_side: bool,
45    pub is_client_side: bool,
46    pub hydration_id: Option<String>,
47}
48
49impl RenderContext {
50    pub fn new() -> Self {
51        Self {
52            environment: ExecutionEnvironment::Universal,
53            route_params: Properties::new(),
54            query_params: Properties::new(),
55            global_state: Properties::new(),
56            is_server_side: false,
57            is_client_side: false,
58            hydration_id: None,
59        }
60    }
61
62    pub fn server_side() -> Self {
63        Self {
64            is_server_side: true,
65            ..Self::new()
66        }
67    }
68
69    pub fn client_side() -> Self {
70        Self {
71            is_client_side: true,
72            ..Self::new()
73        }
74    }
75
76    pub fn with_route_params(mut self, params: Properties) -> Self {
77        self.route_params = params;
78        self
79    }
80
81    pub fn with_query_params(mut self, params: Properties) -> Self {
82        self.query_params = params;
83        self
84    }
85}
86
87/// レンダリング結果IR
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
89pub struct RenderResultIR {
90    pub html: String,
91    pub css: String,
92    pub js: String,
93    pub hydration_script: Option<String>,
94    pub head_elements: Vec<HeadElementIR>,
95    pub virtual_dom: VirtualNodeIR,
96    pub render_stats: RenderStats,
97}
98
99#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
100pub struct HeadElementIR {
101    pub element_type: HeadElementType,
102    pub attributes: Properties,
103    pub content: Option<String>,
104}
105
106#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
107pub enum HeadElementType {
108    Title,
109    Meta,
110    Link,
111    Script,
112    Style,
113}
114
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
116pub struct RenderStats {
117    pub render_time_ms: u64,
118    pub component_count: usize,
119    pub dom_node_count: usize,
120    pub memory_usage_kb: usize,
121}
122
123/// レンダリングエンジンIR
124#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
125pub struct RenderEngineIR {
126    pub strategies: Vec<RenderStrategy>,
127    pub optimizers: Vec<RenderOptimizer>,
128    pub cache_config: RenderCacheConfig,
129}
130
131#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
132pub enum RenderStrategy {
133    /// サーバーサイドレンダリング
134    SSR,
135    /// 静的サイト生成
136    SSG,
137    /// クライアントサイドレンダリング
138    CSR,
139    /// ストリーミングSSR
140    StreamingSSR,
141    /// プログレッシブハイドレーション
142    ProgressiveHydration,
143}
144
145#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
146pub enum RenderOptimizer {
147    /// コード分割
148    CodeSplitting,
149    /// ツリーシェイキング
150    TreeShaking,
151    /// 遅延読み込み
152    LazyLoading,
153    /// プリロード
154    Preload,
155    /// プリフェッチ
156    Prefetch,
157}
158
159#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
160pub struct RenderCacheConfig {
161    pub enable_cache: bool,
162    pub cache_strategy: CacheStrategy,
163    pub max_cache_size: usize,
164    pub ttl_seconds: u64,
165}
166
167#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
168pub enum CacheStrategy {
169    LRU,
170    LFU,
171    TimeBased,
172    SizeBased,
173}
174
175/// 差分更新IR (仮想DOM差分)
176#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
177pub struct DiffIR {
178    pub patches: Vec<PatchIR>,
179    pub old_tree: VirtualNodeIR,
180    pub new_tree: VirtualNodeIR,
181    pub affected_nodes: Vec<String>,
182}
183
184#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
185pub enum PatchIR {
186    /// ノード追加
187    Insert {
188        parent_id: String,
189        node: VirtualNodeIR,
190        index: usize,
191    },
192    /// ノード削除
193    Remove {
194        node_id: String,
195    },
196    /// ノード更新
197    Update {
198        node_id: String,
199        attributes: Properties,
200        text_content: Option<String>,
201    },
202    /// ノード移動
203    Move {
204        node_id: String,
205        new_parent_id: String,
206        new_index: usize,
207    },
208    /// 属性更新
209    UpdateAttribute {
210        node_id: String,
211        attribute_name: String,
212        new_value: Option<Value>,
213    },
214}
215
216/// コンポーネントライフサイクルIR
217#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
218pub enum LifecycleEventIR {
219    Mount {
220        component_id: String,
221        props: Properties,
222    },
223    Update {
224        component_id: String,
225        old_props: Properties,
226        new_props: Properties,
227    },
228    Unmount {
229        component_id: String,
230    },
231    Error {
232        component_id: String,
233        error: String,
234        error_boundary_id: Option<String>,
235    },
236    Suspend {
237        component_id: String,
238        fallback: VirtualNodeIR,
239    },
240    Resume {
241        component_id: String,
242    },
243}
244
245/// レンダリングパイプラインIR
246#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
247pub struct RenderPipelineIR {
248    pub stages: Vec<RenderStage>,
249    pub error_handling: ErrorHandlingStrategy,
250    pub performance_monitoring: bool,
251}
252
253#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
254pub enum RenderStage {
255    /// コンポーネント解決
256    ResolveComponents,
257    /// Propsマッピング
258    MapProps,
259    /// 状態初期化
260    InitializeState,
261    /// 仮想DOM構築
262    BuildVirtualDOM,
263    /// 最適化適用
264    ApplyOptimizations,
265    /// HTML生成
266    GenerateHTML,
267    /// ハイドレーションスクリプト生成
268    GenerateHydrationScript,
269    /// バンドル処理
270    BundleAssets,
271}
272
273#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
274pub enum ErrorHandlingStrategy {
275    /// 即時失敗
276    FailFast,
277    /// エラーバウンダリ使用
278    UseErrorBoundaries,
279    /// フォールバックコンテンツ
280    FallbackContent,
281    /// ログ記録のみ
282    LogOnly,
283}
284
285/// Suspense境界IR
286#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
287pub struct SuspenseBoundaryIR {
288    pub id: String,
289    pub children: Vec<VirtualNodeIR>,
290    pub fallback: VirtualNodeIR,
291    pub pending_promises: Vec<String>,
292    pub resolved: bool,
293}
294
295impl SuspenseBoundaryIR {
296    pub fn new(id: String, fallback: VirtualNodeIR) -> Self {
297        Self {
298            id,
299            children: Vec::new(),
300            fallback,
301            pending_promises: Vec::new(),
302            resolved: false,
303        }
304    }
305
306    pub fn add_child(&mut self, child: VirtualNodeIR) {
307        self.children.push(child);
308    }
309
310    pub fn add_promise(&mut self, promise_id: String) {
311        self.pending_promises.push(promise_id);
312    }
313
314    pub fn resolve_promise(&mut self, promise_id: &str) {
315        self.pending_promises.retain(|id| id != promise_id);
316        if self.pending_promises.is_empty() {
317            self.resolved = true;
318        }
319    }
320}
321
322/// ハイドレーションIR
323#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
324pub struct HydrationIR {
325    pub server_html: String,
326    pub client_script: String,
327    pub hydration_map: HashMap<String, HydrationNode>,
328    pub event_listeners: Vec<EventListenerIR>,
329}
330
331#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
332pub struct HydrationNode {
333    pub id: String,
334    pub component_type: ComponentType,
335    pub props: Properties,
336    pub state: Properties,
337}
338
339#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
340pub struct EventListenerIR {
341    pub element_id: String,
342    pub event_type: String,
343    pub handler_function: String,
344    pub options: EventOptions,
345}
346
347#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
348pub struct EventOptions {
349    pub capture: bool,
350    pub once: bool,
351    pub passive: bool,
352}
353
354/// メモ化IR (React.memo相当)
355#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
356pub struct MemoizationIR {
357    pub component_id: String,
358    pub comparison_function: Option<String>,
359    pub cached_props: Properties,
360    pub cache_hit: bool,
361}
362
363impl MemoizationIR {
364    pub fn new(component_id: String) -> Self {
365        Self {
366            component_id,
367            comparison_function: None,
368            cached_props: Properties::new(),
369            cache_hit: false,
370        }
371    }
372
373    pub fn should_update(&self, new_props: &Properties) -> bool {
374        // シンプルな比較(実際の実装ではcomparison_functionを使用)
375        &self.cached_props != new_props
376    }
377
378    pub fn update_cache(&mut self, new_props: Properties) {
379        self.cached_props = new_props;
380        self.cache_hit = true;
381    }
382}
383
384#[cfg(test)]
385mod tests {
386    use super::*;
387    use crate::frontend::component_ir::ComponentIR;
388
389    #[test]
390    fn test_virtual_node_creation() {
391        let element = VirtualNodeIR::element("div".to_string());
392        match element {
393            VirtualNodeIR::Element(el) => assert_eq!(el.tag_name, "div"),
394            _ => panic!("Expected Element"),
395        }
396
397        let text = VirtualNodeIR::text("Hello".to_string());
398        match text {
399            VirtualNodeIR::Text(content) => assert_eq!(content, "Hello"),
400            _ => panic!("Expected Text"),
401        }
402    }
403
404    #[test]
405    fn test_render_context() {
406        let context = RenderContext::server_side()
407            .with_route_params({
408                let mut props = Properties::new();
409                props.insert("id".to_string(), Value::String("123".to_string()));
410                props
411            });
412
413        assert!(context.is_server_side);
414        assert_eq!(context.route_params.get("id"), Some(&Value::String("123".to_string())));
415    }
416
417    #[test]
418    fn test_suspense_boundary() {
419        let fallback = VirtualNodeIR::text("Loading...".to_string());
420        let mut boundary = SuspenseBoundaryIR::new("suspense-1".to_string(), fallback);
421
422        boundary.add_promise("promise-1".to_string());
423        boundary.add_promise("promise-2".to_string());
424
425        assert_eq!(boundary.pending_promises.len(), 2);
426        assert!(!boundary.resolved);
427
428        boundary.resolve_promise("promise-1");
429        assert_eq!(boundary.pending_promises.len(), 1);
430        assert!(!boundary.resolved);
431
432        boundary.resolve_promise("promise-2");
433        assert_eq!(boundary.pending_promises.len(), 0);
434        assert!(boundary.resolved);
435    }
436
437    #[test]
438    fn test_memoization() {
439        let mut memo = MemoizationIR::new("MyComponent".to_string());
440
441        let mut new_props = Properties::new();
442        new_props.insert("count".to_string(), Value::Int(1));
443
444        assert!(memo.should_update(&new_props));
445
446        memo.update_cache(new_props.clone());
447        assert!(!memo.should_update(&new_props)); // 同じpropsなので更新不要
448
449        let mut different_props = Properties::new();
450        different_props.insert("count".to_string(), Value::Int(2));
451        assert!(memo.should_update(&different_props)); // 異なるpropsなので更新必要
452    }
453}