use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::Instant;
use std::collections::HashMap;
use std::collections::HashSet;
#[derive(Debug, Clone, PartialEq)]
pub enum ModuleType {
JavaScript,
TypeScript,
JSX,
TSX,
CSS,
JSON,
HTML,
WASM,
}
#[derive(Debug, Clone)]
pub struct Module {
pub id: String,
pub path: PathBuf,
pub code: String,
pub module_type: ModuleType,
pub dependencies: Vec<String>,
pub exports: Vec<String>,
pub size: usize,
}
#[derive(Debug, Clone)]
pub struct Chunk {
pub id: String,
pub modules: Vec<String>,
pub size: usize,
pub is_entry: bool,
}
#[derive(Debug, Clone)]
pub struct BundleOutput {
pub chunks: Vec<Chunk>,
pub total_size: usize,
pub entry_points: Vec<String>,
pub assets: HashMap<String, Vec<u8>>,
}
#[derive(Debug, Clone)]
pub struct BuildOptions {
pub minify: bool,
pub sourcemap: bool,
pub target: String,
pub format: String, pub splitting: bool,
pub tree_shaking: bool,
pub optimization_level: u8, pub parallel_jobs: usize,
}
#[derive(Debug, Clone)]
pub struct BuildStats {
pub total_modules: usize,
pub build_time_ms: u64,
pub bundle_size: usize,
pub throughput_mbps: f64,
pub phases: HashMap<String, u64>,
}
pub struct Bundler {
modules: Arc<Mutex<HashMap<String, Module>>>,
options: BuildOptions,
stats: Arc<Mutex<BuildStats>>,
}
impl Bundler {
pub fn new(options: BuildOptions) -> Self {
Self {
modules: Arc::new(Mutex::new(HashMap::new())),
options,
stats: Arc::new(Mutex::new(BuildStats {
total_modules: 0,
build_time_ms: 0,
bundle_size: 0,
throughput_mbps: 0.0,
phases: HashMap::new(),
})),
}
}
pub fn add_module(&self, module: Module) -> Result<()> {
let mut modules = self.modules.lock().unwrap();
modules.insert(module.id.clone(), module);
Ok(())
}
pub fn get_modules(&self) -> Vec<Module> {
let modules: _ = self.modules.lock().unwrap();
modules.values().cloned().collect()
}
pub fn analyze_dependencies(&self, code: &str, module_type: &ModuleType) -> Vec<String> {
let mut dependencies = Vec::new();
match module_type {
ModuleType::JavaScript | ModuleType::TypeScript | ModuleType::JSX | ModuleType::TSX => {
for line in code.lines() {
let line: _ = line.trim();
if let Some(start) = line.find("from '") {
if let Some(end) = line[start + 6..].find('\'') {
let dep: _ = &line[start + 6..start + 6 + end];
dependencies.push(dep.to_string());
}
}
if let Some(start) = line.find("require('") {
if let Some(end) = line[start + 9..].find('\'') {
let dep: _ = &line[start + 9..start + 9 + end];
dependencies.push(dep.to_string());
}
}
}
}
ModuleType::JSON | ModuleType::CSS | ModuleType::HTML => {
}
ModuleType::WASM => {
}
}
dependencies
}
pub fn build(&self, entry_points: Vec<String>) -> Result<BundleOutput> {
let start_time: _ = std::time::Instant::now();
let phase1_start: _ = std::time::Instant::now();
let resolved_modules: _ = self.resolve_dependencies(entry_points.clone())?;
let phase1_time: _ = phase1_start.elapsed().as_millis() as u64;
let phase2_start: _ = std::time::Instant::now();
let transformed_modules: _ = self.transform_modules(resolved_modules)?;
let phase2_time: _ = phase2_start.elapsed().as_millis() as u64;
let phase3_start: _ = std::time::Instant::now();
let chunks: _ = if self.options.splitting {
self.code_splitting(&transformed_modules)?
} else {
vec![self.create_single_chunk(&transformed_modules)?]
};
let phase3_time: _ = phase3_start.elapsed().as_millis() as u64;
let phase4_start: _ = std::time::Instant::now();
let final_chunks: _ = if self.options.tree_shaking {
self.tree_shaking(chunks)?
} else {
chunks
};
let phase4_time: _ = phase4_start.elapsed().as_millis() as u64;
let phase5_start: _ = std::time::Instant::now();
let minified_chunks: _ = if self.options.minify {
self.minify_chunks(&final_chunks)?
} else {
final_chunks
};
let phase5_time: _ = phase5_start.elapsed().as_millis() as u64;
let total_time: _ = start_time.elapsed().as_millis() as u64;
let total_size: usize = minified_chunks.iter().map(|c| c.size).sum();
{
let mut stats = self.stats.lock().unwrap();
stats.total_modules = transformed_modules.len();
stats.build_time_ms = total_time;
stats.bundle_size = total_size;
stats.throughput_mbps = (total_size as f64 / 1024.0 / 1024.0) / (total_time as f64 / 1000.0);
stats.phases.insert("dependency_resolution".to_string(), phase1_time);
stats.phases.insert("transformation".to_string(), phase2_time);
stats.phases.insert("code_splitting".to_string(), phase3_time);
stats.phases.insert("tree_shaking".to_string(), phase4_time);
stats.phases.insert("minification".to_string(), phase5_time);
}
Ok(BundleOutput {
chunks: minified_chunks,
total_size,
entry_points,
assets: HashMap::new(),
})
}
fn resolve_dependencies(&self, entry_points: Vec<String>) -> Result<Vec<Module>> {
let modules: _ = self.modules.lock().unwrap();
let mut resolved = Vec::new();
let mut visited = HashSet::new();
let mut queue = entry_points;
while let Some(entry) = queue.pop() {
if visited.contains(&entry) {
continue;
}
visited.insert(entry.clone());
if let Some(module) = modules.get(&entry) {
resolved.push(module.clone());
for dep in &module.dependencies {
if !visited.contains(dep) {
queue.push(dep.clone());
}
}
}
}
Ok(resolved)
}
fn transform_modules(&self, modules: Vec<Module>) -> Result<Vec<Module>> {
let mut transformed = Vec::new();
for mut module in modules {
if module.module_type == ModuleType::TypeScript || module.module_type == ModuleType::TSX {
module.code = self.typescript_to_javascript(&module.code);
module.module_type = if module.module_type == ModuleType::TypeScript {
ModuleType::JavaScript
} else {
ModuleType::JSX
};
}
transformed.push(module);
}
Ok(transformed)
}
fn typescript_to_javascript(&self, code: &str) -> String {
let mut result = code.to_string();
result = result.replace(": string", "");
result = result.replace(": number", "");
result = result.replace(": boolean", "");
result = result.replace(": any", "");
let lines: Vec<&str> = result.lines()
.filter(|line| {
let line: _ = line.trim();
!line.starts_with("interface") && !line.starts_with("}")
})
.collect();
result = lines.join("\n");
result
}
fn create_single_chunk(&self, modules: &[Module]) -> Result<Chunk> {
let mut code = String::new();
let mut module_ids = Vec::new();
for module in modules {
module_ids.push(module.id.clone());
code.push_str(&format!("// Module: {}\n", module.id));
code.push_str(&module.code);
code.push('\n');
}
Ok(Chunk {
id: "main".to_string(),
modules: module_ids,
size: code.len(),
is_entry: true,
})
}
fn code_splitting(&self, modules: &[Module]) -> Result<Vec<Chunk>> {
Ok(vec![self.create_single_chunk(modules)?])
}
fn tree_shaking(&self, chunks: Vec<Chunk>) -> Result<Vec<Chunk>> {
Ok(chunks)
}
fn minify_chunks(&self, chunks: &[Chunk]) -> Result<Vec<Chunk>> {
let mut minified = Vec::new();
for chunk in chunks {
let mut code = String::new();
for module_id in &chunk.modules {
let modules: _ = self.modules.lock().unwrap();
if let Some(module) = modules.get(module_id) {
let lines: Vec<&str> = module.code.lines()
.filter(|line| !line.trim_start().starts_with("//"))
.collect();
let module_code: _ = lines.join("\n");
code.push_str(&module_code);
code.push('\n');
}
}
minified.push(Chunk {
id: chunk.id.clone(),
modules: chunk.modules.clone(),
size: code.len(),
is_entry: chunk.is_entry,
});
}
Ok(minified)
}
pub fn get_stats(&self) -> BuildStats {
self.stats.lock().unwrap().clone()
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_bundler_creation() {
let options: _ = BuildOptions {
minify: false,
sourcemap: false,
target: "es2020".to_string(),
format: "esm".to_string(),
splitting: false,
tree_shaking: false,
optimization_level: 1,
parallel_jobs: 4,
};
let bundler: _ = Bundler::new(options);
assert_eq!(bundler.options.minify, false);
assert_eq!(bundler.options.parallel_jobs, 4);
}
#[test]
fn test_add_module() {
let options: _ = BuildOptions {
minify: false,
sourcemap: true,
target: "es2020".to_string(),
format: "esm".to_string(),
splitting: true,
tree_shaking: false,
optimization_level: 1,
parallel_jobs: 4,
};
let bundler: _ = Bundler::new(options);
let module: _ = Module {
id: "test.js".to_string(),
path: PathBuf::from("test.js"),
code: "console.log('test');".to_string(),
module_type: ModuleType::JavaScript,
dependencies: Vec::new(),
exports: Vec::new(),
size: 23,
};
assert!(bundler.add_module(module).is_ok());
}
#[test]
fn test_analyze_dependencies() {
let options: _ = BuildOptions {
minify: false,
sourcemap: true,
target: "es2020".to_string(),
format: "esm".to_string(),
splitting: false,
tree_shaking: true,
optimization_level: 1,
parallel_jobs: 4,
};
let bundler: _ = Bundler::new(options);
let code: _ = r#"
import fs from 'fs';
import path from 'path';
const util = require('util');
"#;
let deps: _ = bundler.analyze_dependencies(code, &ModuleType::JavaScript);
assert_eq!(deps.len(), 3);
assert!(deps.contains(&"fs".to_string()));
assert!(deps.contains(&"path".to_string()));
assert!(deps.contains(&"util".to_string()));
}
#[test]
fn test_typescript_to_javascript() {
let options: _ = BuildOptions {
minify: true,
sourcemap: false,
target: "es2020".to_string(),
format: "cjs".to_string(),
splitting: false,
tree_shaking: true,
optimization_level: 3,
parallel_jobs: 1,
};
let bundler: _ = Bundler::new(options);
let ts_code: _ = r#"
function greet(name: string): string {
return `Hello, ${name}`;
}
"#;
let js_code: _ = bundler.typescript_to_javascript(ts_code);
assert!(!js_code.contains(": string"));
assert!(js_code.contains("function greet"));
}
#[test]
fn test_build_stats() {
let options: _ = BuildOptions {
minify: true,
sourcemap: true,
target: "es2020".to_string(),
format: "esm".to_string(),
splitting: true,
tree_shaking: true,
optimization_level: 3,
parallel_jobs: 8,
};
let bundler: _ = Bundler::new(options);
let module: _ = Module {
id: "test.js".to_string(),
path: PathBuf::from("test.js"),
code: "console.log('test');".to_string(),
module_type: ModuleType::JavaScript,
dependencies: Vec::new(),
exports: Vec::new(),
size: 23,
};
bundler.add_module(module).unwrap();
let result: _ = bundler.build(vec!["test.js".to_string()]);
assert!(result.is_ok());
let stats: _ = bundler.get_stats();
assert_eq!(stats.total_modules, 1);
assert!(stats.build_time_ms >= 0); assert!(stats.throughput_mbps >= 0.0);
}
}