dumpling 0.1.0

A fast JavaScript runtime and bundler in Rust
Documentation
use crate::error::{DumplingError, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;

#[derive(Debug, Deserialize, Serialize)]
pub struct ImportMap {
    pub imports: HashMap<String, String>,
    #[serde(rename = "scopes")]
    pub scopes: Option<HashMap<String, HashMap<String, String>>>,
}

impl ImportMap {
    pub fn load(path: &PathBuf) -> Result<Self> {
        let content = std::fs::read_to_string(path)?;
        let import_map: ImportMap = serde_json::from_str(&content)
            .map_err(|e| DumplingError::Build(format!("Invalid import map: {}", e)))?;
        Ok(import_map)
    }

    pub fn resolve(&self, specifier: &str) -> Option<String> {
        // Check exact match first
        if let Some(resolved) = self.imports.get(specifier) {
            return Some(resolved.clone());
        }

        // Check for subpath pattern (e.g., "lodash/": "https://cdn.skypack.dev/lodash/")
        for (key, value) in &self.imports {
            if key.ends_with('/') && specifier.starts_with(key) {
                let subpath = specifier.trim_start_matches(key);
                return Some(format!("{}{}", value, subpath));
            }
        }

        None
    }
}

pub struct ImportMapResolver {
    import_map: Option<ImportMap>,
}

impl ImportMapResolver {
    pub fn new(root: &PathBuf) -> Self {
        let import_map_path = root.join("importmap.json");
        let import_map = if import_map_path.exists() {
            Some(ImportMap::load(&import_map_path).unwrap_or_else(|_| {
                println!(
                    "Warning: Failed to load import map from {}",
                    import_map_path.display()
                );
                ImportMap {
                    imports: HashMap::new(),
                    scopes: None,
                }
            }))
        } else {
            None
        };

        Self { import_map }
    }

    pub fn resolve(&self, specifier: &str) -> Option<String> {
        if let Some(ref import_map) = self.import_map {
            import_map.resolve(specifier)
        } else {
            None
        }
    }
}