use crate::js::loader::ModuleLoader;
use crate::js::module::{ModulePath, ModuleSource};
use crate::js::transpiler::TypeScript;
use crate::prelude::*;
use path_absolutize::Absolutize;
use std::ffi::OsString;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
static FILE_EXTENSIONS: &[&str] =
&["js", "mjs", "jsx", "ts", "tsx", "json", "wasm"];
#[derive(Default)]
pub struct FsModuleLoader;
fn path_not_found<P>(path: P) -> String
where
P: Into<OsString> + std::fmt::Debug,
{
format!("Error: Module path {path:?} not found!")
}
fn path_not_found2<P>(path: P, e: std::io::Error) -> String
where
P: Into<OsString> + std::fmt::Debug,
{
format!("Error: Module path {path:?} not found: {e:?}")
}
impl FsModuleLoader {
fn transform(&self, path: PathBuf) -> String {
path.into_os_string().into_string().unwrap()
}
fn is_json_import(&self, path: &Path) -> bool {
path
.extension()
.map(|value| value == "json")
.unwrap_or(false)
}
fn wrap_json(&self, source: &str) -> String {
format!("export default JSON.parse(`{source}`);")
}
fn load_source(&self, path: &Path) -> AnyResult<ModuleSource> {
let source = fs::read_to_string(path)?;
let source = if self.is_json_import(path) {
self.wrap_json(source.as_str())
} else {
source
};
Ok(source)
}
fn load_as_file(&self, path: &Path) -> AnyResult<(PathBuf, ModuleSource)> {
if path.is_file() {
return match self.load_source(path) {
Ok(source) => Ok((path.to_path_buf(), source)),
Err(e) => Err(e),
};
}
if path.extension().is_none() {
for ext in FILE_EXTENSIONS {
let ext_path = path.with_extension(ext);
if ext_path.is_file() {
return match self.load_source(&ext_path) {
Ok(source) => Ok((ext_path.to_path_buf(), source)),
Err(e) => Err(e),
};
}
}
}
anyhow::bail!(path_not_found(path));
}
fn load_as_directory(
&self,
path: &Path,
) -> AnyResult<(PathBuf, ModuleSource)> {
for ext in FILE_EXTENSIONS {
let path = &path.join(format!("index.{ext}"));
if path.is_file() {
return match self.load_source(path) {
Ok(source) => Ok((path.to_path_buf(), source)),
Err(e) => Err(e),
};
}
}
anyhow::bail!(path_not_found(path));
}
}
impl ModuleLoader for FsModuleLoader {
fn resolve(
&self,
base: Option<&str>,
specifier: &str,
) -> AnyResult<ModulePath> {
if specifier.starts_with('/')
|| WINDOWS_DRIVE_BEGIN_REGEX.is_match(specifier)
{
return Ok(
self.transform(Path::new(specifier).absolutize()?.to_path_buf()),
);
}
if specifier.starts_with("./") || specifier.starts_with("../") {
let base = match base {
Some(value) => Path::new(value).parent().unwrap().to_path_buf(),
None => {
anyhow::bail!(path_not_found(specifier))
}
};
return Ok(
self.transform(base.join(specifier).absolutize()?.to_path_buf()),
);
}
match PATH_CONFIG.config_home() {
Some(config_home) => {
let simple_specifier = config_home.join(specifier);
match simple_specifier.absolutize() {
Ok(simple_path) => {
if simple_path.exists() {
return Ok(self.transform(simple_path.to_path_buf()));
}
}
Err(e) => {
anyhow::bail!(path_not_found2(specifier, e))
}
}
let npm_specifier = config_home.join("node_modules").join(specifier);
match npm_specifier.absolutize() {
Ok(npm_path) => {
if npm_path.exists() {
return Ok(self.transform(npm_path.to_path_buf()));
}
}
Err(e) => {
anyhow::bail!(path_not_found2(specifier, e))
}
}
anyhow::bail!(path_not_found(specifier));
}
None => {
anyhow::bail!(path_not_found(specifier));
}
}
}
fn load(&self, specifier: &str) -> AnyResult<ModuleSource> {
let path = Path::new(specifier);
let maybe_source = self
.load_as_file(path)
.or_else(|_| self.load_as_directory(path));
let (path, source) = match maybe_source {
Ok((path, source)) => (path, source),
Err(_) => {
anyhow::bail!(path_not_found(path))
}
};
let path_extension = path.extension().unwrap().to_str().unwrap();
let fname = path.to_str();
match path_extension {
"ts" => TypeScript::compile(fname, &source),
_ => Ok(source),
}
}
}