use crate::graph::Range;
use crate::module_specifier::resolve_import;
use crate::text_encoding::strip_bom_mut;
use anyhow::anyhow;
use anyhow::Error;
use anyhow::Result;
use data_url::DataUrl;
use deno_ast::ModuleSpecifier;
#[cfg(feature = "rust")]
use futures::future;
use futures::future::Future;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::fmt;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
pub static DEFAULT_JSX_IMPORT_SOURCE_MODULE: &str = "jsx-runtime";
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
pub struct CacheInfo {
pub local: Option<PathBuf>,
pub emit: Option<PathBuf>,
pub map: Option<PathBuf>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "camelCase")]
pub enum LoadResponse {
External { specifier: ModuleSpecifier },
Module {
content: Arc<str>,
specifier: ModuleSpecifier,
#[serde(rename = "headers", skip_serializing_if = "Option::is_none")]
maybe_headers: Option<HashMap<String, String>>,
},
}
pub type LoadResult = Result<Option<LoadResponse>>;
pub type LoadFuture = Pin<Box<dyn Future<Output = LoadResult> + 'static>>;
pub trait Loader {
fn get_cache_info(&self, _specifier: &ModuleSpecifier) -> Option<CacheInfo> {
None
}
fn load(
&mut self,
specifier: &ModuleSpecifier,
is_dynamic: bool,
) -> LoadFuture;
}
pub trait Resolver: fmt::Debug {
fn default_jsx_import_source(&self) -> Option<String> {
None
}
fn jsx_import_source_module(&self) -> &str {
DEFAULT_JSX_IMPORT_SOURCE_MODULE
}
fn resolve(
&self,
specifier: &str,
referrer: &ModuleSpecifier,
) -> Result<ModuleSpecifier, Error> {
Ok(resolve_import(specifier, referrer)?)
}
fn resolve_types(
&self,
_specifier: &ModuleSpecifier,
) -> Result<Option<(ModuleSpecifier, Option<Range>)>> {
Ok(None)
}
}
pub fn load_data_url(
specifier: &ModuleSpecifier,
) -> Result<Option<LoadResponse>> {
let url = DataUrl::process(specifier.as_str())
.map_err(|_| anyhow!("Unable to decode data url."))?;
let (bytes, _) = url
.decode_to_vec()
.map_err(|_| anyhow!("Unable to decode data url."))?;
let mut headers: HashMap<String, String> = HashMap::new();
headers.insert("content-type".to_string(), url.mime_type().to_string());
let mut content = String::from_utf8(bytes)?;
strip_bom_mut(&mut content);
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: Some(headers),
content: content.into(),
}))
}
#[cfg(feature = "rust")]
pub struct MemoryLoader {
sources: HashMap<ModuleSpecifier, Result<LoadResponse, Error>>,
cache_info: HashMap<ModuleSpecifier, CacheInfo>,
}
#[cfg(feature = "rust")]
pub enum Source<S> {
Module {
specifier: S,
maybe_headers: Option<Vec<(S, S)>>,
content: S,
},
External(S),
Err(Error),
}
#[cfg(feature = "rust")]
pub type MemoryLoaderSources<S> = Vec<(S, Source<S>)>;
#[cfg(feature = "rust")]
impl MemoryLoader {
pub fn new<S: AsRef<str>>(
sources: MemoryLoaderSources<S>,
cache_info: Vec<(S, CacheInfo)>,
) -> Self {
Self {
sources: sources
.into_iter()
.map(|(s, r)| {
let specifier = ModuleSpecifier::parse(s.as_ref()).unwrap();
let result = match r {
Source::Module {
specifier,
maybe_headers,
content,
} => Ok(LoadResponse::Module {
specifier: ModuleSpecifier::parse(specifier.as_ref()).unwrap(),
maybe_headers: maybe_headers.map(|h| {
h.into_iter()
.map(|(k, v)| {
(k.as_ref().to_string(), v.as_ref().to_string())
})
.collect()
}),
content: content.as_ref().into(),
}),
Source::External(specifier) => Ok(LoadResponse::External {
specifier: ModuleSpecifier::parse(specifier.as_ref()).unwrap(),
}),
Source::Err(error) => Err(error),
};
(specifier, result)
})
.collect(),
cache_info: cache_info
.into_iter()
.map(|(s, c)| {
let specifier = ModuleSpecifier::parse(s.as_ref()).unwrap();
(specifier, c)
})
.collect(),
}
}
}
#[cfg(feature = "rust")]
impl Loader for MemoryLoader {
fn get_cache_info(&self, specifier: &ModuleSpecifier) -> Option<CacheInfo> {
self.cache_info.get(specifier).cloned()
}
fn load(
&mut self,
specifier: &ModuleSpecifier,
_is_dynamic: bool,
) -> LoadFuture {
let response = match self.sources.get(specifier) {
Some(Ok(response)) => Ok(Some(response.clone())),
Some(Err(err)) => Err(anyhow!("{}", err)),
None if specifier.scheme() == "data" => load_data_url(specifier),
_ => Ok(None),
};
Box::pin(future::ready(response))
}
}
pub trait Reporter: fmt::Debug {
fn on_load(
&self,
specifier: &ModuleSpecifier,
modules_done: usize,
modules_total: usize,
);
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::module_specifier::resolve_import;
use serde_json::json;
#[derive(Debug)]
pub(crate) struct MockResolver {
map: HashMap<ModuleSpecifier, HashMap<String, ModuleSpecifier>>,
types: HashMap<ModuleSpecifier, (ModuleSpecifier, Option<Range>)>,
}
impl MockResolver {
pub fn new<S: AsRef<str>>(
map: Vec<(S, Vec<(S, S)>)>,
types: Vec<(S, (S, Option<Range>))>,
) -> Self {
Self {
map: map
.into_iter()
.map(|(r, m)| {
let referrer = ModuleSpecifier::parse(r.as_ref()).unwrap();
let map = m
.into_iter()
.map(|(s, ms)| {
let specifier_str = s.as_ref().to_string();
let specifier = ModuleSpecifier::parse(ms.as_ref()).unwrap();
(specifier_str, specifier)
})
.collect();
(referrer, map)
})
.collect(),
types: types
.into_iter()
.map(|(s, (t, ms))| {
let specifier = ModuleSpecifier::parse(s.as_ref()).unwrap();
let types_specifier = ModuleSpecifier::parse(t.as_ref()).unwrap();
(specifier, (types_specifier, ms))
})
.collect(),
}
}
}
impl Resolver for MockResolver {
fn resolve(
&self,
specifier: &str,
referrer: &ModuleSpecifier,
) -> Result<ModuleSpecifier, Error> {
if let Some(map) = self.map.get(referrer) {
if let Some(resolved_specifier) = map.get(specifier) {
return Ok(resolved_specifier.clone());
}
}
Ok(resolve_import(specifier, referrer)?)
}
fn resolve_types(
&self,
specifier: &ModuleSpecifier,
) -> Result<Option<(ModuleSpecifier, Option<Range>)>> {
Ok(self.types.get(specifier).cloned())
}
}
#[test]
fn test_deserialize_load_response() {
let actual: LoadResponse = serde_json::from_value(
json!({ "kind": "external", "specifier": "https://example.com/bundle" }),
)
.unwrap();
assert_eq!(
actual,
LoadResponse::External {
specifier: ModuleSpecifier::parse("https://example.com/bundle")
.unwrap()
}
);
}
}