1use kotoba_core::prelude::*;
6use crate::frontend::component_ir::ComponentIR;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57pub enum OptimizationIR {
58 CodeSplitting {
60 strategy: CodeSplittingStrategy,
61 chunks: Vec<ChunkConfig>,
62 },
63 TreeShaking {
65 side_effects: Vec<String>,
66 unused_exports: bool,
67 },
68 Minification {
70 compressor: CompressorType,
71 mangle: bool,
72 },
73 Compression {
75 algorithm: CompressionAlgorithm,
76 level: CompressionLevel,
77 },
78 ImageOptimization {
80 formats: Vec<ImageFormat>,
81 quality: u8,
82 },
83}
84
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub enum CodeSplittingStrategy {
87 EntryPoint,
89 DynamicImport,
91 SizeBased { max_size_kb: usize },
93 VendorBased,
95}
96
97#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
98pub struct ChunkConfig {
99 pub name: String,
100 pub test: Option<String>, 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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
136pub enum PluginIR {
137 CSS {
139 preprocessor: Option<CSSPreprocessor>,
140 postprocessor: Option<String>,
141 modules: bool,
142 },
143 TypeScript {
145 config_path: Option<String>,
146 transpile_only: bool,
147 },
148 React {
150 runtime: ReactRuntime,
151 fast_refresh: bool,
152 },
153 BundleAnalyzer {
155 open_analyzer: bool,
156 generate_stats_file: bool,
157 },
158 PWA {
160 service_worker: bool,
161 manifest: bool,
162 },
163 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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
186pub enum BuildEnvironment {
187 Development,
188 Production,
189 Test,
190}
191
192#[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>, pub chunks: HashMap<String, Vec<String>>, 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#[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 Eager,
278 Lazy,
280 Viewport,
282 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#[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#[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 match env {
423 BuildEnvironment::Production => {
424 self.output.source_maps = false;
425 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 config.set_environment(BuildEnvironment::Development);
482 assert!(config.output.source_maps);
483
484 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}