#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BrowserTarget {
Modern,
Legacy,
}
#[derive(Debug, Clone)]
pub struct DifferentialConfig {
pub generate_modern: bool,
pub generate_legacy: bool,
pub modern_module_type: String,
pub legacy_module_type: String,
}
impl Default for DifferentialConfig {
fn default() -> Self {
Self {
generate_modern: true,
generate_legacy: true,
modern_module_type: "es".to_string(),
legacy_module_type: "system".to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct BundleVariant {
pub code: String,
pub target: BrowserTarget,
pub filename: String,
}
pub struct DifferentialLoader {
config: DifferentialConfig,
}
impl DifferentialLoader {
pub fn new(config: DifferentialConfig) -> Self {
Self { config }
}
pub fn generate_bundles(&self, modern_code: &str, base_filename: &str) -> Vec<BundleVariant> {
let mut bundles = Vec::new();
if self.config.generate_modern {
bundles.push(BundleVariant {
code: self.generate_modern_bundle(modern_code),
target: BrowserTarget::Modern,
filename: format!("{}.modern.js", base_filename),
});
}
if self.config.generate_legacy {
bundles.push(BundleVariant {
code: self.generate_legacy_bundle(modern_code),
target: BrowserTarget::Legacy,
filename: format!("{}.legacy.js", base_filename),
});
}
bundles
}
fn generate_modern_bundle(&self, code: &str) -> String {
let mut bundle = String::new();
bundle.push_str("// Windjammer Modern Bundle (ES2015+)\n");
bundle.push_str("// Chrome 60+, Firefox 60+, Safari 12+, Edge 79+\n\n");
bundle.push_str(code);
bundle
}
fn generate_legacy_bundle(&self, code: &str) -> String {
let mut bundle = String::new();
bundle.push_str("// Windjammer Legacy Bundle (ES5)\n");
bundle.push_str("// IE 11+, older browsers\n\n");
let transpiled = self.transpile_to_es5(code);
bundle.push_str(&self.generate_legacy_polyfills());
bundle.push('\n');
bundle.push_str(&transpiled);
bundle
}
fn transpile_to_es5(&self, code: &str) -> String {
let mut result = code.to_string();
result = result.replace("() =>", "function()");
result = result.replace(") =>", ") { return");
result = result.replace("const ", "var ");
result = result.replace("let ", "var ");
result = result.replace("`${", "\" + (");
result = result.replace("}`", ") + \"");
result = result.replace("`", "\"");
result
}
fn generate_legacy_polyfills(&self) -> String {
r#"// Legacy Polyfills
if (!Array.prototype.includes) {
Array.prototype.includes = function(item) {
return this.indexOf(item) !== -1;
};
}
if (!String.prototype.startsWith) {
String.prototype.startsWith = function(search) {
return this.substr(0, search.length) === search;
};
}
if (!String.prototype.endsWith) {
String.prototype.endsWith = function(search) {
return this.substr(-search.length) === search;
};
}
if (!Object.assign) {
Object.assign = function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
}
return target;
};
}
"#
.to_string()
}
pub fn generate_html_loader(&self, base_filename: &str) -> String {
format!(
r#"<!-- Windjammer Differential Loading -->
<script type="module">
// Modern browsers (supports ES modules)
import './{}.modern.js';
</script>
<script nomodule>
// Legacy browsers (fallback)
document.write('<script src="{}.legacy.js"><\/script>');
</script>
"#,
base_filename, base_filename
)
}
}
pub fn generate_differential_bundles(
code: &str,
base_filename: &str,
config: DifferentialConfig,
) -> Vec<BundleVariant> {
let loader = DifferentialLoader::new(config);
loader.generate_bundles(code, base_filename)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_differential_config() {
let config = DifferentialConfig::default();
assert!(config.generate_modern);
assert!(config.generate_legacy);
}
#[test]
fn test_generate_bundles() {
let code = "const x = () => 42;";
let config = DifferentialConfig::default();
let loader = DifferentialLoader::new(config);
let bundles = loader.generate_bundles(code, "app");
assert_eq!(bundles.len(), 2);
assert_eq!(bundles[0].target, BrowserTarget::Modern);
assert_eq!(bundles[1].target, BrowserTarget::Legacy);
}
#[test]
fn test_transpile_to_es5() {
let code = "const x = 42;";
let config = DifferentialConfig::default();
let loader = DifferentialLoader::new(config);
let transpiled = loader.transpile_to_es5(code);
assert!(transpiled.contains("var"));
assert!(!transpiled.contains("const"));
}
#[test]
fn test_html_loader_generation() {
let config = DifferentialConfig::default();
let loader = DifferentialLoader::new(config);
let html = loader.generate_html_loader("app");
assert!(html.contains("type=\"module\""));
assert!(html.contains("nomodule"));
assert!(html.contains(".modern.js"));
assert!(html.contains(".legacy.js"));
}
}