deno-utils 0.7.0

Utility functions for deno.
Documentation
use deno_core::anyhow::bail;
use deno_core::error::AnyError;
use deno_core::futures::FutureExt;
use deno_core::resolve_import;
use deno_core::ModuleLoader;
use deno_core::ModuleSource;
use deno_core::ModuleSourceFuture;
use deno_core::ModuleSpecifier;
use deno_core::ModuleType;
#[cfg(feature = "bundle")]
use deno_graph::source::{LoadFuture, LoadResponse, Loader};
#[cfg(feature = "transpile")]
use deno_transpiler::compile;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;

use crate::FsModuleStore;
use crate::{get_source_code, ModuleStore, UniversalModuleLoader};

impl Default for UniversalModuleLoader {
    fn default() -> Self {
        Self {
            store: Some(Arc::new(FsModuleStore::default())),
            compile: true,
        }
    }
}

impl UniversalModuleLoader {
    pub fn new(module_store: Option<Arc<dyn ModuleStore>>, compile: bool) -> Self {
        Self {
            store: module_store,
            compile,
        }
    }

    pub async fn get_and_update_source(
        self,
        m: &ModuleSpecifier,
        #[allow(unused_variables)] minify: bool,
    ) -> Result<String, AnyError> {
        #[allow(unused_mut)]
        let mut code = get_source_code(m).await?;
        #[cfg(feature = "transpile")]
        if self.compile {
            code = compile(m, code, minify)?;
        }
        if let Some(store) = self.store.as_ref() {
            store.put(m.to_string(), code.as_bytes()).await?;
        }
        Ok(code)
    }
}

impl ModuleLoader for UniversalModuleLoader {
    fn resolve(
        &self,
        specifier: &str,
        referrer: &str,
        _is_main: bool,
    ) -> Result<ModuleSpecifier, AnyError> {
        Ok(resolve_import(specifier, referrer)?)
    }

    fn load(
        &self,
        module_specifier: &ModuleSpecifier,
        _maybe_referrer: Option<ModuleSpecifier>,
        _is_dyn_import: bool,
    ) -> Pin<Box<ModuleSourceFuture>> {
        let m = module_specifier.clone();
        let string_specifier = m.to_string();

        let loader = self.clone();
        async move {
            let module_type = get_module_type(&m)?;
            if let Some(store) = loader.store.as_ref() {
                if let Ok(code) = store.get(&string_specifier).await {
                    return Ok(ModuleSource {
                        code,
                        module_type,
                        module_url_specified: string_specifier.clone(),
                        module_url_found: string_specifier,
                    });
                }
            }
            let code = loader.get_and_update_source(&m, false).await?;

            Ok(ModuleSource {
                code: code.into_bytes().into_boxed_slice(),
                module_type,
                module_url_specified: string_specifier.clone(),
                module_url_found: string_specifier,
            })
        }
        .boxed_local()
    }
}

#[cfg(feature = "bundle")]
impl Loader for UniversalModuleLoader {
    fn load(&mut self, specifier: &ModuleSpecifier, _is_dynamic: bool) -> LoadFuture {
        let loader = self.clone();
        let m = specifier.clone();
        async move {
            let code = loader.get_and_update_source(&m, false).await?;
            Ok(Some(LoadResponse::Module {
                content: code.into(),
                specifier: m,
                maybe_headers: None,
            }))
        }
        .boxed_local()
    }
}

fn get_module_type(m: &ModuleSpecifier) -> Result<ModuleType, AnyError> {
    let path = if let Ok(path) = m.to_file_path() {
        path
    } else {
        PathBuf::from(m.path())
    };
    match path.extension() {
        Some(ext) => {
            let lowercase_str = ext.to_str().map(|s| s.to_lowercase());
            match lowercase_str.as_deref() {
                Some("json") => Ok(ModuleType::Json),
                None => bail!("Unknown extension"),
                _ => Ok(ModuleType::JavaScript),
            }
        }
        None => bail!("Unknown media type {:?}", path),
    }
}

#[cfg(test)]
mod tests {
    use deno_core::resolve_url_or_path;

    use super::*;
    use crate::test_util::testdata_path;
    #[tokio::test]
    async fn universal_loader_should_work() {
        let p = testdata_path("esm_imports_a.js");
        let m = resolve_url_or_path(&p).unwrap();
        let store = FsModuleStore::default();
        let loader = UniversalModuleLoader::new(Some(Arc::new(store.clone())), false);
        let content = loader.get_and_update_source(&m, false).await.unwrap();
        let expected = include_str!("../../../fixtures/testdata/esm_imports_a.js");
        assert_eq!(content, expected);

        let cache = store.get(m.as_str()).await.unwrap();
        assert_eq!(cache, expected.as_bytes().to_vec().into_boxed_slice());
    }
}