pub mod es_module;
pub mod import_map;
pub mod module_map;
#[cfg(test)]
mod module_map_tests;
use crate::js::JsRuntime;
use crate::js::binding;
use crate::js::loader::AsyncFsModuleLoader;
use crate::js::loader::AsyncModuleLoader;
use crate::js::loader::CoreModuleLoader;
use crate::js::loader::FsModuleLoader;
use crate::js::loader::ModuleLoader;
use crate::prelude::*;
use crate::util::paths;
use std::sync::LazyLock;
pub use es_module::*;
pub use import_map::*;
pub use module_map::*;
pub type ModulePath = String;
pub type ModuleSource = String;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModuleStatus {
Fetching,
Resolving,
Duplicate,
Ready,
}
pub static CORE_MODULES: LazyLock<FoldMap<&'static str, &'static str>> =
LazyLock::new(|| {
let modules = vec![
];
FoldMap::from_iter(modules)
});
pub fn create_origin<'s>(
scope: &mut v8::PinScope<'s, '_>,
name: &str,
is_module: bool,
) -> v8::ScriptOrigin<'s> {
let name = v8::String::new(scope, name).unwrap();
let source_map = v8::undefined(scope);
v8::ScriptOrigin::new(
scope,
name.into(),
0,
0,
false,
0,
Some(source_map.into()),
false,
false,
is_module,
None,
)
}
fn _choose_module_loader(specifier: &str) -> &dyn ModuleLoader {
static CORE_MODULE_LOADER: CoreModuleLoader = CoreModuleLoader {};
static FS_MODULE_LOADER: LazyLock<FsModuleLoader> =
LazyLock::new(FsModuleLoader::new);
let is_core_module_import = CORE_MODULES.contains_key(specifier);
if is_core_module_import {
&CORE_MODULE_LOADER
} else {
&*FS_MODULE_LOADER
}
}
pub fn resolve_import(
base: &str,
specifier: &str,
import_map: Option<ImportMap>,
) -> TheResult<ModulePath> {
let specifier = match import_map {
Some(map) => map.lookup(specifier).unwrap_or_else(|| specifier.into()),
None => specifier.into(),
};
let resolver: &dyn ModuleLoader = _choose_module_loader(specifier.as_str());
resolver.resolve(base, &specifier)
}
pub fn load_import(
specifier: &str,
_skip_cache: bool,
) -> TheResult<ModuleSource> {
let loader: &dyn ModuleLoader = _choose_module_loader(specifier);
loader.load(specifier)
}
pub async fn async_load_import(
specifier: &str,
_skip_cache: bool,
) -> TheResult<ModuleSource> {
static ASYNC_FS_MODULE_LOADER: AsyncFsModuleLoader = AsyncFsModuleLoader {};
let loader: &dyn AsyncModuleLoader = &ASYNC_FS_MODULE_LOADER;
loader.load(specifier).await
}
pub fn fetch_module<'s>(
scope: &mut v8::PinScope<'s, '_>,
filename: &str,
source: Option<&str>,
) -> Option<v8::Local<'s, v8::Module>> {
let origin = create_origin(scope, filename, true);
let source = match source {
Some(source) => source.into(),
None => load_import(filename, true).unwrap(),
};
let source = v8::String::new(scope, &source).unwrap();
let mut source = v8::script_compiler::Source::new(source, Some(&origin));
v8::script_compiler::compile_module(scope, &mut source)
}
pub fn fetch_module_tree<'s>(
scope: &mut v8::PinScope<'s, '_>,
filename: &str,
source: Option<&str>,
) -> Option<v8::Local<'s, v8::Module>> {
let module = match fetch_module(scope, filename, source) {
Some(module) => module,
None => {
return None;
}
};
let state_rc = JsRuntime::state(scope);
let module_id = module.get_identity_hash().get();
let module_ref = v8::Global::new(scope, module);
{
let mut state = state_rc.borrow_mut();
state.module_map.insert(filename, module_id, module_ref);
state
.module_map
.seen
.insert(filename.into(), ModuleStatus::Ready);
trace!(
"|fetch_module_tree| ModuleMap seen {:?} {:?}",
filename,
ModuleStatus::Ready
);
}
let requests = module.get_module_requests();
for i in 0..requests.length() {
let request = requests.get(scope, i).unwrap();
let request = v8::Local::<v8::ModuleRequest>::try_from(request).unwrap();
let base = paths::maybe_parent(&filename).to_string_lossy();
let specifier = request.get_specifier().to_rust_string_lossy(scope);
let specifier = match resolve_import(&base, &specifier, None) {
Ok(specifier) => specifier,
Err(e) => {
binding::throw_exception(scope, &e);
return None;
}
};
if !state_rc.borrow().module_map.contains(&specifier) {
fetch_module_tree(scope, &specifier, None)?;
}
}
Some(module)
}