kotoba_server/frontend/
build_ir.rs

1//! ビルド/バンドルシステムIR定義
2//!
3//! コード分割、バンドル設定、最適化を表現します。
4
5use kotoba_core::prelude::*;
6use crate::frontend::component_ir::ComponentIR;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10/// ビルド設定IR
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub struct BuildConfigIR {
13    pub entry_points: Vec<EntryPoint>,
14    pub output: OutputConfig,
15    pub bundler: BundlerType,
16    pub optimizations: Vec<OptimizationIR>,
17    pub plugins: Vec<PluginIR>,
18    pub environment: BuildEnvironment,
19}
20
21#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22pub struct EntryPoint {
23    pub name: String,
24    pub path: String,
25    pub component_type: EntryPointType,
26}
27
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29pub enum EntryPointType {
30    Page,
31    Component,
32    Layout,
33    Middleware,
34    API,
35}
36
37#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38pub struct OutputConfig {
39    pub directory: String,
40    pub filename_pattern: String,
41    pub public_path: String,
42    pub source_maps: bool,
43    pub clean: bool,
44}
45
46#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
47pub enum BundlerType {
48    Webpack,
49    Vite,
50    Rollup,
51    Esbuild,
52    Custom(String),
53}
54
55/// 最適化IR
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57pub enum OptimizationIR {
58    /// コード分割
59    CodeSplitting {
60        strategy: CodeSplittingStrategy,
61        chunks: Vec<ChunkConfig>,
62    },
63    /// Tree Shaking
64    TreeShaking {
65        side_effects: Vec<String>,
66        unused_exports: bool,
67    },
68    /// ミニファイ
69    Minification {
70        compressor: CompressorType,
71        mangle: bool,
72    },
73    /// 圧縮
74    Compression {
75        algorithm: CompressionAlgorithm,
76        level: CompressionLevel,
77    },
78    /// 画像最適化
79    ImageOptimization {
80        formats: Vec<ImageFormat>,
81        quality: u8,
82    },
83}
84
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub enum CodeSplittingStrategy {
87    /// エントリーポイントベース
88    EntryPoint,
89    /// 動的インポートベース
90    DynamicImport,
91    /// サイズベース
92    SizeBased { max_size_kb: usize },
93    /// ベンダーベース
94    VendorBased,
95}
96
97#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
98pub struct ChunkConfig {
99    pub name: String,
100    pub test: Option<String>, // 正規表現パターン
101    pub priority: i32,
102    pub enforce: bool,
103}
104
105#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
106pub enum CompressorType {
107    Terser,
108    UglifyJS,
109    SWC,
110}
111
112#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
113pub enum CompressionAlgorithm {
114    Gzip,
115    Brotli,
116    Deflate,
117}
118
119#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
120pub enum CompressionLevel {
121    Fastest = 1,
122    Default = 6,
123    Best = 9,
124}
125
126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
127pub enum ImageFormat {
128    WebP,
129    AVIF,
130    JPEG,
131    PNG,
132}
133
134/// プラグインIR
135#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
136pub enum PluginIR {
137    /// CSS処理
138    CSS {
139        preprocessor: Option<CSSPreprocessor>,
140        postprocessor: Option<String>,
141        modules: bool,
142    },
143    /// TypeScript
144    TypeScript {
145        config_path: Option<String>,
146        transpile_only: bool,
147    },
148    /// React
149    React {
150        runtime: ReactRuntime,
151        fast_refresh: bool,
152    },
153    /// バンドル分析
154    BundleAnalyzer {
155        open_analyzer: bool,
156        generate_stats_file: bool,
157    },
158    /// PWA
159    PWA {
160        service_worker: bool,
161        manifest: bool,
162    },
163    /// カスタムプラグイン
164    Custom {
165        name: String,
166        config: Properties,
167    },
168}
169
170#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
171pub enum CSSPreprocessor {
172    Sass,
173    Less,
174    Stylus,
175    PostCSS,
176}
177
178#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
179pub enum ReactRuntime {
180    Automatic,
181    Classic,
182}
183
184/// ビルド環境
185#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
186pub enum BuildEnvironment {
187    Development,
188    Production,
189    Test,
190}
191
192/// バンドル結果IR
193#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
194pub struct BundleResultIR {
195    pub chunks: Vec<ChunkIR>,
196    pub assets: Vec<AssetIR>,
197    pub stats: BuildStats,
198    pub manifest: BundleManifest,
199}
200
201#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
202pub struct ChunkIR {
203    pub id: String,
204    pub name: Option<String>,
205    pub entry: bool,
206    pub initial: bool,
207    pub files: Vec<String>,
208    pub hash: ContentHash,
209    pub size: usize,
210    pub modules: Vec<ModuleIR>,
211}
212
213#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
214pub struct ModuleIR {
215    pub id: String,
216    pub name: String,
217    pub size: usize,
218    pub dependencies: Vec<String>,
219    pub is_entry: bool,
220    pub chunks: Vec<String>,
221}
222
223#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
224pub struct AssetIR {
225    pub name: String,
226    pub path: String,
227    pub size: usize,
228    pub content_type: String,
229    pub hash: ContentHash,
230}
231
232#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
233pub struct BuildStats {
234    pub build_time_ms: u64,
235    pub total_size: usize,
236    pub gzip_size: usize,
237    pub brotli_size: usize,
238    pub chunk_count: usize,
239    pub module_count: usize,
240    pub asset_count: usize,
241    pub warnings: Vec<String>,
242    pub errors: Vec<String>,
243}
244
245#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
246pub struct BundleManifest {
247    pub entries: HashMap<String, String>, // entry_name -> chunk_id
248    pub chunks: HashMap<String, Vec<String>>, // chunk_id -> asset_paths
249    pub modules: HashMap<String, ModuleManifest>,
250}
251
252#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
253pub struct ModuleManifest {
254    pub file: String,
255    pub exports: Vec<String>,
256    pub imports: Vec<String>,
257}
258
259/// コード分割IR
260#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
261pub struct CodeSplittingIR {
262    pub dynamic_imports: Vec<DynamicImportIR>,
263    pub lazy_components: Vec<LazyComponentIR>,
264    pub preload_hints: Vec<PreloadHintIR>,
265}
266
267#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
268pub struct DynamicImportIR {
269    pub module_path: String,
270    pub chunk_name: Option<String>,
271    pub loading_strategy: LoadingStrategy,
272}
273
274#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
275pub enum LoadingStrategy {
276    /// 即時読み込み
277    Eager,
278    /// 遅延読み込み
279    Lazy,
280    /// ビューポート内読み込み
281    Viewport,
282    /// インタラクション時読み込み
283    Interaction,
284}
285
286#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
287pub struct LazyComponentIR {
288    pub component_id: String,
289    pub import_path: String,
290    pub fallback: Option<ComponentIR>,
291    pub loading_strategy: LoadingStrategy,
292}
293
294#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
295pub struct PreloadHintIR {
296    pub resource_path: String,
297    pub resource_type: ResourceType,
298    pub priority: PreloadPriority,
299}
300
301#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
302pub enum ResourceType {
303    Script,
304    Style,
305    Font,
306    Image,
307    Document,
308}
309
310#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
311pub enum PreloadPriority {
312    High,
313    Medium,
314    Low,
315}
316
317/// デプロイメントIR
318#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
319pub struct DeploymentIR {
320    pub strategy: DeploymentStrategy,
321    pub cdn_config: Option<CDNConfig>,
322    pub cache_config: CacheConfig,
323    pub monitoring: MonitoringConfig,
324}
325
326#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
327pub enum DeploymentStrategy {
328    StaticHosting,
329    ServerSideRendering,
330    EdgeFunctions,
331    Hybrid,
332}
333
334#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
335pub struct CDNConfig {
336    pub provider: CDNProvider,
337    pub distribution_id: String,
338    pub regions: Vec<String>,
339}
340
341#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
342pub enum CDNProvider {
343    CloudFlare,
344    AWSCloudFront,
345    Vercel,
346    Netlify,
347    Custom(String),
348}
349
350#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
351pub struct CacheConfig {
352    pub static_cache_ttl: u64,
353    pub api_cache_ttl: u64,
354    pub cache_invalidation: CacheInvalidationStrategy,
355}
356
357#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
358pub enum CacheInvalidationStrategy {
359    TimeBased,
360    TagBased,
361    Manual,
362}
363
364#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
365pub struct MonitoringConfig {
366    pub performance_monitoring: bool,
367    pub error_tracking: bool,
368    pub analytics: bool,
369    pub real_user_monitoring: bool,
370}
371
372/// 開発サーバーIR
373#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
374pub struct DevServerIR {
375    pub port: u16,
376    pub host: String,
377    pub hot_reload: bool,
378    pub fast_refresh: bool,
379    pub proxy_config: Vec<ProxyRule>,
380    pub middleware: Vec<String>,
381}
382
383#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
384pub struct ProxyRule {
385    pub path_pattern: String,
386    pub target_url: String,
387    pub change_origin: bool,
388}
389
390impl BuildConfigIR {
391    pub fn new(bundler: BundlerType) -> Self {
392        Self {
393            entry_points: Vec::new(),
394            output: OutputConfig {
395                directory: "dist".to_string(),
396                filename_pattern: "[name].[contenthash].js".to_string(),
397                public_path: "/".to_string(),
398                source_maps: true,
399                clean: true,
400            },
401            bundler,
402            optimizations: Vec::new(),
403            plugins: Vec::new(),
404            environment: BuildEnvironment::Development,
405        }
406    }
407
408    pub fn add_entry_point(&mut self, entry: EntryPoint) {
409        self.entry_points.push(entry);
410    }
411
412    pub fn add_optimization(&mut self, optimization: OptimizationIR) {
413        self.optimizations.push(optimization);
414    }
415
416    pub fn add_plugin(&mut self, plugin: PluginIR) {
417        self.plugins.push(plugin);
418    }
419
420    pub fn set_environment(&mut self, env: BuildEnvironment) {
421        // 環境に応じて最適化設定を調整
422        match env {
423            BuildEnvironment::Production => {
424                self.output.source_maps = false;
425                // プロダクション最適化を追加
426                self.add_optimization(OptimizationIR::Minification {
427                    compressor: CompressorType::Terser,
428                    mangle: true,
429                });
430                self.add_optimization(OptimizationIR::Compression {
431                    algorithm: CompressionAlgorithm::Gzip,
432                    level: CompressionLevel::Best,
433                });
434            }
435            BuildEnvironment::Development => {
436                self.output.source_maps = true;
437            }
438            BuildEnvironment::Test => {
439                self.output.source_maps = true;
440            }
441        }
442        self.environment = env;
443    }
444}
445
446#[cfg(test)]
447mod tests {
448    use super::*;
449
450    #[test]
451    fn test_build_config_creation() {
452        let mut config = BuildConfigIR::new(BundlerType::Vite);
453
454        config.add_entry_point(EntryPoint {
455            name: "main".to_string(),
456            path: "src/main.tsx".to_string(),
457            component_type: EntryPointType::Page,
458        });
459
460        config.add_optimization(OptimizationIR::CodeSplitting {
461            strategy: CodeSplittingStrategy::DynamicImport,
462            chunks: Vec::new(),
463        });
464
465        config.add_plugin(PluginIR::React {
466            runtime: ReactRuntime::Automatic,
467            fast_refresh: true,
468        });
469
470        assert_eq!(config.bundler, BundlerType::Vite);
471        assert_eq!(config.entry_points.len(), 1);
472        assert_eq!(config.optimizations.len(), 1);
473        assert_eq!(config.plugins.len(), 1);
474    }
475
476    #[test]
477    fn test_environment_configuration() {
478        let mut config = BuildConfigIR::new(BundlerType::Webpack);
479
480        // 開発環境
481        config.set_environment(BuildEnvironment::Development);
482        assert!(config.output.source_maps);
483
484        // プロダクション環境
485        config.set_environment(BuildEnvironment::Production);
486        assert!(!config.output.source_maps);
487        assert!(config.optimizations.iter().any(|opt| matches!(opt, OptimizationIR::Minification { .. })));
488    }
489
490    #[test]
491    fn test_code_splitting() {
492        let mut config = BuildConfigIR::new(BundlerType::Rollup);
493
494        config.add_optimization(OptimizationIR::CodeSplitting {
495            strategy: CodeSplittingStrategy::SizeBased { max_size_kb: 244 },
496            chunks: vec![
497                ChunkConfig {
498                    name: "vendor".to_string(),
499                    test: Some("node_modules".to_string()),
500                    priority: 10,
501                    enforce: true,
502                },
503            ],
504        });
505
506        match &config.optimizations[0] {
507            OptimizationIR::CodeSplitting { strategy, chunks } => {
508                match strategy {
509                    CodeSplittingStrategy::SizeBased { max_size_kb } => {
510                        assert_eq!(*max_size_kb, 244);
511                    }
512                    _ => panic!("Expected SizeBased strategy"),
513                }
514                assert_eq!(chunks.len(), 1);
515                assert_eq!(chunks[0].name, "vendor");
516            }
517            _ => panic!("Expected CodeSplitting optimization"),
518        }
519    }
520}