#![allow(unused_imports)]
#![allow(deprecated)]
#![allow(dead_code)]
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
path::Path,
path::PathBuf,
rc::Rc,
sync::{Arc, RwLock},
};
use deno_core::{
error::{AnyError, ModuleLoaderError},
futures::FutureExt,
url::ParseError,
FastString, ModuleLoadResponse, ModuleResolutionError, ModuleSource, ModuleSourceCode,
ModuleSpecifier, ModuleType,
};
use deno_error::JsErrorBox;
use crate::{
module_loader::{ClonableSource, ModuleCacheProvider},
traits::ToModuleSpecifier,
transpiler::{transpile, transpile_extension, ExtensionTranspilation},
Error,
};
#[cfg(feature = "node_experimental")]
use crate::ext::node::resolvers::{RustyNpmPackageFolderResolver, RustyResolver};
#[cfg(feature = "node_experimental")]
use crate::ext::node::NodeCodeTranslator;
#[cfg(feature = "node_experimental")]
use deno_node::NodeResolver;
#[cfg(feature = "node_experimental")]
use node_resolver::InNpmPackageChecker;
#[cfg(feature = "node_experimental")]
use node_resolver::{NodeResolutionKind, ResolutionMode};
use super::ImportProvider;
type SourceMapCache = HashMap<String, (String, Option<Vec<u8>>)>;
#[derive(Default)]
pub struct LoaderOptions {
pub cache_provider: Option<Box<dyn ModuleCacheProvider>>,
pub fs_whitelist: HashSet<String>,
pub source_map_cache: SourceMapCache,
#[cfg(feature = "node_experimental")]
pub node_resolver: Arc<RustyResolver>,
pub import_provider: Option<Box<dyn ImportProvider>>,
pub schema_whlist: HashSet<String>,
pub cwd: PathBuf,
}
#[cfg(feature = "node_experimental")]
struct NodeProvider {
rusty_resolver: Arc<RustyResolver>,
node_resolver: Arc<
NodeResolver<
deno_resolver::npm::DenoInNpmPackageChecker,
RustyNpmPackageFolderResolver,
sys_traits::impls::RealSys,
>,
>,
code_translator: Rc<NodeCodeTranslator>,
}
#[cfg(feature = "node_experimental")]
impl NodeProvider {
pub fn new(resolver: Arc<RustyResolver>) -> Self {
let node_resolver = resolver.node_resolver();
let code_translator = Rc::new(resolver.code_translator(node_resolver.clone()));
Self {
rusty_resolver: resolver,
node_resolver,
code_translator,
}
}
}
pub struct InnerRustyLoader {
cache_provider: Option<Box<dyn ModuleCacheProvider>>,
fs_whlist: HashSet<String>,
source_map_cache: SourceMapCache,
import_provider: Option<Box<dyn ImportProvider>>,
schema_whlist: HashSet<String>,
cwd: PathBuf,
#[cfg(feature = "node_experimental")]
node: NodeProvider,
}
impl InnerRustyLoader {
pub fn new(options: LoaderOptions) -> Self {
Self {
cache_provider: options.cache_provider,
fs_whlist: options.fs_whitelist,
source_map_cache: options.source_map_cache,
import_provider: options.import_provider,
schema_whlist: options.schema_whlist,
cwd: options.cwd,
#[cfg(feature = "node_experimental")]
node: NodeProvider::new(options.node_resolver),
}
}
pub fn set_current_dir(&mut self, cwd: PathBuf) {
self.cwd = cwd;
}
pub fn whitelist_add(&mut self, specifier: &str) {
self.fs_whlist.insert(specifier.to_string());
}
pub fn whitelist_has(&self, specifier: &str) -> bool {
self.fs_whlist.contains(specifier)
}
#[allow(clippy::unused_self)]
pub fn transpile_extension(
&self,
specifier: &FastString,
code: &FastString,
) -> Result<ExtensionTranspilation, JsErrorBox> {
let specifier = specifier
.as_str()
.to_module_specifier(&self.cwd)
.map_err(|e| JsErrorBox::from_err(std::io::Error::other(e)))?;
let code = code.as_str();
transpile_extension(&specifier, code)
}
pub fn resolve(
&mut self,
specifier: &str,
referrer: &str,
kind: deno_core::ResolutionKind,
) -> Result<ModuleSpecifier, ModuleLoaderError> {
#[cfg(feature = "node_experimental")]
let referrer_specifier = if deno_core::specifier_has_uri_scheme(referrer) {
deno_core::resolve_url(referrer).map_err(JsErrorBox::from_err)?
} else {
referrer
.to_module_specifier(&self.cwd)
.map_err(JsErrorBox::from_err)?
};
#[cfg(feature = "node_experimental")]
if let Some(alias) = self.handle_alias(specifier, referrer)? {
return Ok(alias);
}
#[cfg(feature = "node_experimental")]
if is_builtin_node_module(specifier) {
return self.load_npm(specifier, referrer);
}
#[cfg(feature = "node_experimental")]
if referrer_specifier.scheme() == "file"
&& self.node.rusty_resolver.in_npm_package(&referrer_specifier)
{
return self.load_npm(specifier, referrer);
}
let url =
deno_core::resolve_import(specifier, referrer).map_err(ModuleLoaderError::from_err)?;
if self
.cache_provider
.as_ref()
.is_some_and(|c| c.get(&url).is_some())
{
return Ok(url);
}
if let Some(import_provider) = &mut self.import_provider {
let resolve_result = import_provider.resolve(&url, referrer, kind);
if let Some(result) = resolve_result {
return result;
}
}
if referrer == "." {
self.whitelist_add(url.as_str());
}
match url.scheme() {
"https" | "http" => {
#[cfg(not(feature = "url_import"))]
return Err(JsErrorBox::from_err(Error::Runtime(format!(
"{specifier} imports are not allowed here"
))));
}
"file" =>
{
#[cfg(not(feature = "fs_import"))]
if !self.whitelist_has(url.as_str()) {
return Err(JsErrorBox::from_err(Error::Runtime(format!(
"module {url} is not loaded"
))));
}
}
_ if specifier.starts_with("ext:") => {
}
#[cfg(feature = "node_experimental")]
_ if specifier.starts_with("npm:") || specifier.starts_with("node:") => {
return self.load_npm(specifier, referrer);
}
_ if self.schema_whlist.iter().any(|s| specifier.starts_with(s)) => {
}
_ => {
let error = Error::Runtime(format!("unsupported scheme: {}", url.scheme()));
return Err(JsErrorBox::from_err(error));
}
}
Ok(url)
}
pub fn load(
inner: Rc<RefCell<Self>>,
module_specifier: &ModuleSpecifier,
maybe_referrer: Option<&ModuleSpecifier>,
is_dyn_import: bool,
requested_module_type: deno_core::RequestedModuleType,
) -> deno_core::ModuleLoadResponse {
let module_specifier = module_specifier.clone();
let maybe_referrer = maybe_referrer.cloned();
if let Some(cache) = &inner.borrow().cache_provider {
if let Some(source) = cache.get(&module_specifier) {
return deno_core::ModuleLoadResponse::Sync(Ok(source));
}
}
let provider_result = inner.borrow_mut().import_provider.as_mut().and_then(|p| {
p.import(
&module_specifier,
maybe_referrer.as_ref(),
is_dyn_import,
requested_module_type,
)
});
if let Some(result) = provider_result {
return ModuleLoadResponse::Async(
async move {
Self::handle_load(inner, module_specifier, |_, _| async move { result }).await
}
.boxed_local(),
);
}
match module_specifier.scheme() {
#[cfg(feature = "url_import")]
"https" | "http" => ModuleLoadResponse::Async(
async move { Self::handle_load(inner, module_specifier, Self::load_remote).await }
.boxed_local(),
),
"file" => ModuleLoadResponse::Async(
async move { Self::handle_load(inner, module_specifier, Self::load_file).await }
.boxed_local(),
),
x => {
let error =
Error::Runtime(format!("unsupported scheme: {x} for {module_specifier}"));
ModuleLoadResponse::Sync(Err(JsErrorBox::from_err(error)))
}
}
}
#[allow(unused_variables)]
#[allow(clippy::unused_async)]
pub async fn translate_cjs(
inner: Rc<RefCell<Self>>,
module_specifier: ModuleSpecifier,
content: String,
) -> Result<String, Error> {
#[cfg(not(feature = "node_experimental"))]
{
Ok(content)
}
#[cfg(feature = "node_experimental")]
{
let is_npm = inner
.borrow()
.node
.rusty_resolver
.in_npm_package(&module_specifier);
if is_npm {
let translator = inner.borrow().node.code_translator.clone();
let source = translator
.translate_cjs_to_esm(
&module_specifier,
Some(std::borrow::Cow::Borrowed(&content)),
)
.await?
.into_owned();
Ok(source)
} else {
Ok(content)
}
}
}
#[cfg(feature = "node_experimental")]
fn handle_alias(
&self,
specifier: &str,
referrer: &str,
) -> Result<Option<ModuleSpecifier>, ModuleLoaderError> {
if specifier.starts_with('#') {
let referrer = if deno_core::specifier_has_uri_scheme(referrer) {
deno_core::resolve_url(referrer).map_err(JsErrorBox::from_err)?
} else {
referrer
.to_module_specifier(&self.cwd)
.map_err(JsErrorBox::from_err)?
};
let Ok(referrer_path) = referrer.to_file_path() else {
return Ok(None);
};
let package = self
.node
.rusty_resolver
.package_json_resolver()
.get_closest_package_json(&referrer_path)
.map_err(JsErrorBox::from_err)?;
if let Some(package) = package {
let referrer = node_resolver::UrlOrPathRef::from_url(&referrer);
let url = self
.node
.node_resolver
.resolve_package_import(
specifier,
Some(&referrer),
Some(&package),
ResolutionMode::Import,
NodeResolutionKind::Execution,
)
.map_err(JsErrorBox::from_err)?;
let url = match url {
node_resolver::UrlOrPath::Url(url) => url,
node_resolver::UrlOrPath::Path(path) => {
let url = format!("file://{}", path.to_string_lossy());
deno_core::resolve_url(&url).map_err(JsErrorBox::from_err)?
}
};
return Ok(Some(url));
}
}
Ok(None)
}
#[cfg(feature = "node_experimental")]
fn load_npm(
&self,
specifier: &str,
referrer: &str,
) -> Result<ModuleSpecifier, ModuleLoaderError> {
let referrer = if deno_core::specifier_has_uri_scheme(referrer) {
deno_core::resolve_url(referrer).map_err(JsErrorBox::from_err)?
} else {
referrer
.to_module_specifier(&self.cwd)
.map_err(JsErrorBox::from_err)?
};
let (_, specifier) = specifier.split_once(':').unwrap_or(("", specifier));
let resolution = self
.node
.node_resolver
.resolve(
specifier,
&referrer,
ResolutionMode::Import,
NodeResolutionKind::Execution,
)
.map_err(JsErrorBox::from_err)?;
let url = resolution.into_url().map_err(JsErrorBox::from_err)?;
Ok(url)
}
#[allow(unused_variables)]
async fn load_file(
inner: Rc<RefCell<Self>>,
module_specifier: ModuleSpecifier,
) -> Result<String, ModuleLoaderError> {
let path = module_specifier.to_file_path().map_err(|()| {
JsErrorBox::from_err(Error::Runtime(format!(
"{module_specifier} is not a file path"
)))
})?;
let content = tokio::fs::read_to_string(path)
.await
.map_err(ModuleLoaderError::from_err)?;
let content = Self::translate_cjs(inner, module_specifier, content)
.await
.map_err(ModuleLoaderError::from_err)?;
Ok(content)
}
#[cfg(feature = "url_import")]
async fn load_remote(
_: Rc<RefCell<Self>>,
module_specifier: ModuleSpecifier,
) -> Result<String, ModuleLoaderError> {
let response = reqwest::get(module_specifier)
.await
.map_err(|e| ModuleLoaderError::generic(e.to_string()))?;
let response = response
.text()
.await
.map_err(|e| ModuleLoaderError::generic(e.to_string()))?;
Ok(response)
}
async fn handle_load<F, Fut>(
inner: Rc<RefCell<Self>>,
module_specifier: ModuleSpecifier,
handler: F,
) -> Result<ModuleSource, ModuleLoaderError>
where
F: FnOnce(Rc<RefCell<Self>>, ModuleSpecifier) -> Fut,
Fut: std::future::Future<Output = Result<String, ModuleLoaderError>>,
{
if let Some(Some(source)) = inner
.borrow()
.cache_provider
.as_ref()
.map(|p| p.get(&module_specifier))
{
return Ok(source);
}
let extension = Path::new(module_specifier.path())
.extension()
.unwrap_or_default();
let module_type = if extension.eq_ignore_ascii_case("json") {
ModuleType::Json
} else {
ModuleType::JavaScript
};
let code = handler(inner.clone(), module_specifier.clone()).await?;
let (tcode, source_map) =
transpile(&module_specifier, &code).map_err(ModuleLoaderError::from_err)?;
let mut source = ModuleSource::new(
module_type,
ModuleSourceCode::String(tcode.into()),
&module_specifier,
None,
);
inner.borrow_mut().add_source_map(
module_specifier.as_str(),
code,
source_map.map(|s| s.to_vec()),
);
if let Some(p) = &mut inner.borrow_mut().cache_provider {
p.set(&module_specifier, source.clone(&module_specifier));
}
if let Some(import_provider) = &mut inner.borrow_mut().import_provider {
source = import_provider.post_process(&module_specifier, source)?;
}
Ok(source)
}
pub fn get_source_map(&self, filename: &str) -> Option<&(String, Option<Vec<u8>>)> {
self.source_map_cache.get(filename)
}
pub fn add_source_map(&mut self, filename: &str, source: String, source_map: Option<Vec<u8>>) {
self.source_map_cache
.insert(filename.to_string(), (source, source_map));
}
}
#[cfg(feature = "node_experimental")]
fn is_builtin_node_module(specifier: &str) -> bool {
use node_resolver::IsBuiltInNodeModuleChecker;
node_resolver::DenoIsBuiltInNodeModuleChecker.is_builtin_node_module(specifier)
}