use crate::cfg::path_cfg::PATH_CONFIG;
use crate::js::loader::AsyncModuleLoader;
use crate::js::loader::ModuleLoader;
use crate::js::module::ModulePath;
use crate::js::module::ModuleSource;
#[cfg(feature = "typescript")]
use crate::js::transpiler::TypeScript;
#[cfg(feature = "wasm")]
use crate::js::transpiler::Wasm;
use crate::prelude::*;
use async_trait::async_trait;
use oxc_resolver::ResolveOptions;
use oxc_resolver::Resolver;
fn is_json_import(path: &Path) -> bool {
path
.extension()
.map(|value| value == "json")
.unwrap_or(false)
}
fn wrap_json(source: &str) -> String {
format!("export default JSON.parse(`{source}`);")
}
mod sync_load {
use super::*;
pub fn load_source(path: &Path) -> TheResult<ModuleSource> {
match std::fs::read_to_string(path) {
Ok(source) => {
let source = if is_json_import(path) {
wrap_json(source.as_str())
} else {
source
};
Ok(source)
}
Err(e) => Err(TheErr::LoadModuleFailed(
path.to_string_lossy().to_string(),
e,
)),
}
}
pub fn load_as_file(path: &Path) -> TheResult<(PathBuf, ModuleSource)> {
if path.is_file() {
return match load_source(path) {
Ok(source) => Ok((path.to_path_buf(), source)),
Err(e) => Err(e),
};
}
Err(TheErr::ModuleNotFound(path.to_string_lossy().to_string()))
}
}
mod async_load {
use super::*;
pub async fn async_load_source(path: &Path) -> TheResult<ModuleSource> {
match tokio::fs::read_to_string(path).await {
Ok(source) => {
let source = if is_json_import(path) {
wrap_json(source.as_str())
} else {
source
};
Ok(source)
}
Err(e) => Err(TheErr::LoadModuleFailed(
path.to_string_lossy().to_string(),
e,
)),
}
}
pub async fn async_load_as_file(
path: &Path,
) -> TheResult<(PathBuf, ModuleSource)> {
if path.is_file() {
return match async_load_source(path).await {
Ok(source) => Ok((path.to_path_buf(), source)),
Err(e) => Err(e),
};
}
Err(TheErr::ModuleNotFound(path.to_string_lossy().to_string()))
}
}
#[allow(dead_code)]
#[derive(Default)]
pub struct FsModuleLoader {
resolver: Resolver,
}
fn create_resolve_opts() -> ResolveOptions {
ResolveOptions {
extensions: vec![
".js".into(),
".ts".into(),
".mjs".into(),
".json".into(),
".wasm".into(),
],
extension_alias: vec![
(".js".into(), vec![".js".into()]),
(".mjs".into(), vec![".mjs".into()]),
(".ts".into(), vec![".ts".into()]),
(".json".into(), vec![".json".into()]),
(".wasm".into(), vec![".wasm".into()]),
],
modules: vec![
PATH_CONFIG.config_home().to_string_lossy().to_string(),
PATH_CONFIG
.config_home()
.join("node_modules")
.to_string_lossy()
.to_string(),
"node_modules".to_string(),
],
..ResolveOptions::default()
}
}
impl FsModuleLoader {
pub fn new() -> Self {
Self {
resolver: Resolver::new(create_resolve_opts()),
}
}
}
impl FsModuleLoader {
fn resolve_impl(
&self,
resolver: &Resolver,
base: &str,
specifier: &str,
) -> TheResult<ModulePath> {
let base = Path::new(base).to_path_buf();
trace!(
"|FsModuleLoader::resolve| base:{:?}, specifier:{:?}",
base, specifier
);
match resolver.resolve(&base, specifier) {
Ok(resolution) => Ok(resolution.path().to_string_lossy().to_string()),
Err(_) => Err(TheErr::ModuleNotFound(specifier.to_string())),
}
}
}
impl ModuleLoader for FsModuleLoader {
#[cfg(not(test))]
fn resolve(&self, base: &str, specifier: &str) -> TheResult<ModulePath> {
self.resolve_impl(&self.resolver, base, specifier)
}
#[cfg(test)]
fn resolve(&self, base: &str, specifier: &str) -> TheResult<ModulePath> {
let resolver = Resolver::new(create_resolve_opts());
self.resolve_impl(&resolver, base, specifier)
}
fn load(&self, specifier: &str) -> TheResult<ModuleSource> {
let path = Path::new(specifier);
let maybe_source = sync_load::load_as_file(path);
let (path, source) = match maybe_source {
Ok((path, source)) => (path, source),
Err(e) => return Err(e),
};
let path_extension =
path.extension().unwrap().to_string_lossy().to_string();
match path_extension.as_str() {
#[cfg(feature = "wasm")]
"wasm" => Ok(Wasm::parse(&source)),
#[cfg(feature = "typescript")]
"ts" => {
let fname = path.to_str();
TypeScript::compile(fname, &source)
}
_ => Ok(source),
}
}
}
#[derive(Default)]
pub struct AsyncFsModuleLoader;
#[async_trait]
impl AsyncModuleLoader for AsyncFsModuleLoader {
async fn load(&self, specifier: &str) -> TheResult<ModuleSource> {
let path = Path::new(specifier);
let maybe_source = async_load::async_load_as_file(path).await;
let (path, source) = match maybe_source {
Ok((path, source)) => (path, source),
Err(e) => return Err(e),
};
let path_extension =
path.extension().unwrap().to_string_lossy().to_string();
match path_extension.as_str() {
#[cfg(feature = "wasm")]
"wasm" => Ok(Wasm::parse(&source)),
#[cfg(feature = "typescript")]
"ts" => {
let fname = path.to_str();
TypeScript::compile(fname, &source)
}
_ => Ok(source),
}
}
}