use std::cell::RefCell;
use std::collections::HashSet;
use std::collections::VecDeque;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::Context;
use std::task::Poll;
use futures::future::FutureExt;
use futures::stream::FuturesUnordered;
use futures::stream::Stream;
use futures::stream::StreamExt;
use futures::stream::TryStreamExt;
use super::loaders::ModuleLoadOptions;
use crate::ModuleLoadResponse;
use crate::ModuleLoader;
use crate::ModuleSource;
use crate::ModuleSourceCode;
use crate::error::CoreError;
use crate::module_specifier::ModuleSpecifier;
use crate::modules::ModuleError;
use crate::modules::ModuleId;
use crate::modules::ModuleImportPhase;
use crate::modules::ModuleLoadId;
use crate::modules::ModuleLoaderError;
use crate::modules::ModuleReference;
use crate::modules::ModuleRequest;
use crate::modules::RequestedModuleType;
use crate::modules::ResolutionKind;
use crate::modules::loaders::ModuleLoadReferrer;
use crate::modules::map::ModuleMap;
use crate::modules::module_map_data::ModuleSourceKind;
use crate::source_map::SourceMapApplication;
use crate::source_map::SourceMapper;
type ModuleLoadFuture = dyn Future<
Output = Result<Option<(ModuleRequest, ModuleSource)>, ModuleLoaderError>,
>;
#[derive(Debug, Copy, Clone)]
pub enum SideModuleKind {
Async,
Sync,
}
#[derive(Debug)]
enum LoadInit {
Main(String),
Side {
specifier: String,
kind: SideModuleKind,
code: Option<String>,
},
DynamicImport(String, String, RequestedModuleType, ModuleImportPhase),
}
#[derive(Debug, Eq, PartialEq)]
enum LoadState {
Init,
LoadingRoot,
LoadingImports,
Done,
}
pub(crate) struct RecursiveModuleLoad {
id: ModuleLoadId,
root_module_id: Option<ModuleId>,
init: LoadInit,
state: LoadState,
module_map_rc: Rc<ModuleMap>,
pending: FuturesUnordered<Pin<Box<ModuleLoadFuture>>>,
visited: HashSet<ModuleReference>,
visited_as_alias: Rc<RefCell<HashSet<String>>>,
root_module_reference: Option<ModuleReference>,
resolved_specifier: Option<Result<ModuleSpecifier, CoreError>>,
loader: Rc<dyn ModuleLoader>,
}
impl Drop for RecursiveModuleLoad {
fn drop(&mut self) {
self.loader.finish_load();
}
}
impl RecursiveModuleLoad {
pub(crate) async fn main(
specifier: String,
module_map_rc: Rc<ModuleMap>,
) -> Result<Self, CoreError> {
let mut load = Self::new(LoadInit::Main(specifier), module_map_rc);
load.prepare().await?;
Ok(load)
}
pub(crate) async fn side(
specifier: String,
module_map_rc: Rc<ModuleMap>,
kind: SideModuleKind,
code: Option<String>,
) -> Result<Self, CoreError> {
let mut load = Self::new(
LoadInit::Side {
specifier,
kind,
code,
},
module_map_rc,
);
load.prepare().await?;
Ok(load)
}
pub(crate) fn dynamic_import(
specifier: String,
referrer: String,
requested_module_type: RequestedModuleType,
phase: ModuleImportPhase,
module_map_rc: Rc<ModuleMap>,
) -> Self {
Self::new(
LoadInit::DynamicImport(
specifier,
referrer,
requested_module_type,
phase,
),
module_map_rc,
)
}
fn new(init: LoadInit, module_map_rc: Rc<ModuleMap>) -> Self {
let id = module_map_rc.next_load_id();
let loader = module_map_rc.loader.borrow().clone();
let requested_module_type = match &init {
LoadInit::DynamicImport(_, _, module_type, _) => module_type.clone(),
_ => RequestedModuleType::None,
};
let resolved_specifier =
Some(Self::resolve_root_from_init(&init, &module_map_rc));
let root_module_id = resolved_specifier
.as_ref()
.and_then(|r| r.as_ref().ok())
.and_then(|spec| {
module_map_rc.get_id(spec.as_str(), requested_module_type)
});
Self {
id,
root_module_id,
init,
state: LoadState::Init,
module_map_rc: module_map_rc.clone(),
loader,
pending: FuturesUnordered::new(),
visited: HashSet::new(),
visited_as_alias: Default::default(),
root_module_reference: None,
resolved_specifier,
}
}
pub(crate) fn id(&self) -> ModuleLoadId {
self.id
}
pub(crate) fn root_module_id(&self) -> Option<ModuleId> {
self.root_module_id
}
pub(crate) fn root_module_reference(&self) -> Option<&ModuleReference> {
self.root_module_reference.as_ref()
}
fn resolve_root_from_init(
init: &LoadInit,
module_map_rc: &ModuleMap,
) -> Result<ModuleSpecifier, CoreError> {
match init {
LoadInit::Main(specifier) => {
module_map_rc.resolve(specifier, ".", ResolutionKind::MainModule)
}
LoadInit::Side { specifier, .. } => {
module_map_rc.resolve(specifier, ".", ResolutionKind::Import)
}
LoadInit::DynamicImport(specifier, referrer, _, _) => module_map_rc
.resolve(specifier, referrer, ResolutionKind::DynamicImport),
}
}
pub(crate) async fn prepare(&mut self) -> Result<(), CoreError> {
let module_specifier = match self.resolved_specifier.take() {
Some(Ok(spec)) => {
self.resolved_specifier = Some(Ok(spec.clone()));
spec
}
Some(Err(err)) => return Err(err),
None => unreachable!("prepare() called after resolution consumed"),
};
let (maybe_referrer, maybe_code, requested_module_type, is_synchronous) =
match &mut self.init {
LoadInit::Main(_) => (None, None, RequestedModuleType::None, false),
LoadInit::Side { kind, code, .. } => (
None,
code.take(),
RequestedModuleType::None,
match kind {
SideModuleKind::Async => false,
SideModuleKind::Sync => true,
},
),
LoadInit::DynamicImport(_, referrer, requested_module_type, _) => (
Some(referrer.to_string()),
None,
requested_module_type.clone(),
false,
),
};
self
.loader
.prepare_load(
&module_specifier,
maybe_referrer,
maybe_code,
ModuleLoadOptions {
is_synchronous,
is_dynamic_import: self.is_dynamic_import(),
requested_module_type,
},
)
.await
.map_err(|e| e.into())
}
fn is_currently_loading_main_module(&self) -> bool {
!self.is_dynamic_import()
&& matches!(self.init, LoadInit::Main(..))
&& self.state == LoadState::LoadingRoot
}
fn is_dynamic_import(&self) -> bool {
matches!(self.init, LoadInit::DynamicImport(..))
}
fn is_synchronous(&self) -> bool {
matches!(
self.init,
LoadInit::Side {
kind: SideModuleKind::Sync,
..
}
)
}
pub(crate) fn register_and_recurse(
&mut self,
scope: &mut v8::PinScope,
module_request: &ModuleRequest,
mut module_source: ModuleSource,
) -> Result<(), ModuleError> {
if let Some(source_kind) =
ModuleSourceKind::from_module_type(&module_source.module_type)
{
match source_kind {
ModuleSourceKind::Wasm => {
module_source = self.module_map_rc.new_wasm_module_source(
scope,
&module_request.reference,
module_source,
)?;
}
}
if module_request.phase == ModuleImportPhase::Source {
if self.pending.is_empty() {
self.state = LoadState::Done;
}
return Ok(());
}
} else if module_request.phase == ModuleImportPhase::Source {
let message = v8::String::new(
scope,
&format!(
"Source phase imports are not supported for {} modules",
&module_source.module_type
),
)
.unwrap();
let exception = v8::Exception::reference_error(scope, message);
return Err(ModuleError::Exception(v8::Global::new(scope, exception)));
}
let code = module_source.cheap_copy_code();
let module_id = self.module_map_rc.new_module(
scope,
self.is_currently_loading_main_module(),
self.is_dynamic_import(),
module_source,
)?;
self.register_and_recurse_inner(
module_id,
&module_request.reference,
Some(&code),
);
if self.state == LoadState::LoadingRoot {
self.root_module_id = Some(module_id);
self.state = LoadState::LoadingImports;
}
if self.pending.is_empty() {
self.state = LoadState::Done;
}
Ok(())
}
fn register_and_recurse_inner(
&mut self,
module_id: usize,
module_reference: &ModuleReference,
code: Option<&ModuleSourceCode>,
) {
let mut already_registered = VecDeque::new();
already_registered.push_back((module_id, module_reference.clone()));
self.visited.insert(module_reference.clone());
while let Some((module_id, module_reference)) =
already_registered.pop_front()
{
let referrer = &module_reference.specifier;
let imports = self
.module_map_rc
.get_requested_modules(module_id)
.unwrap()
.clone();
for module_request in imports {
if !self.visited.contains(&module_request.reference)
&& !self
.visited_as_alias
.borrow()
.contains(module_request.reference.specifier.as_str())
{
match self.module_map_rc.get_id(
module_request.reference.specifier.as_str(),
&module_request.reference.requested_module_type,
) {
Some(module_id) => {
already_registered
.push_back((module_id, module_request.reference.clone()));
}
_ => {
let request = module_request.clone();
let visited_as_alias = self.visited_as_alias.clone();
let referrer = code.and_then(|code| {
let source_offset = request.referrer_source_offset?;
source_mapped_module_load_referrer(
&self.module_map_rc.source_mapper,
referrer,
code,
source_offset,
)
});
let loader = self.loader.clone();
let is_dynamic_import = self.is_dynamic_import();
let is_synchronous = self.is_synchronous();
let requested_module_type =
request.reference.requested_module_type.clone();
let fut = async move {
if visited_as_alias
.borrow()
.contains(request.reference.specifier.as_str())
{
return Ok(None);
}
let load_response = loader.load(
&request.reference.specifier,
referrer.as_ref(),
ModuleLoadOptions {
is_dynamic_import,
is_synchronous,
requested_module_type,
},
);
let load_result = match load_response {
ModuleLoadResponse::Sync(result) => result,
ModuleLoadResponse::Async(fut) => fut.await,
};
if let Ok(source) = &load_result
&& let Some(found_specifier) = &source.module_url_found
{
visited_as_alias
.borrow_mut()
.insert(found_specifier.as_str().to_string());
}
load_result.map(|s| Some((request, s)))
};
self.pending.push(fut.boxed_local());
}
}
self.visited.insert(module_request.reference);
}
}
}
}
pub(crate) async fn run_to_completion(
mut self,
mut on_module: impl FnMut(
&mut Self,
&ModuleRequest,
ModuleSource,
) -> Result<(), CoreError>,
) -> Result<ModuleId, CoreError> {
while let Some(load_result) = self.next().await {
let (request, source) = load_result?;
on_module(&mut self, &request, source)?;
}
Ok(self.root_module_id.expect("Root module should be loaded"))
}
}
impl Stream for RecursiveModuleLoad {
type Item = Result<(ModuleRequest, ModuleSource), CoreError>;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context,
) -> Poll<Option<Self::Item>> {
let inner = self.get_mut();
match inner.state {
LoadState::Init => {
let module_specifier = match inner.resolved_specifier.take() {
Some(Ok(spec)) => spec,
Some(Err(error)) => {
return Poll::Ready(Some(Err(error)));
}
None => {
unreachable!("resolved_specifier should be set in LoadState::Init")
}
};
let (requested_module_type, phase) = match &inner.init {
LoadInit::DynamicImport(_, _, module_type, phase) => {
(module_type.clone(), *phase)
}
_ => (RequestedModuleType::None, ModuleImportPhase::Evaluation),
};
let module_request = ModuleRequest {
reference: ModuleReference {
specifier: module_specifier.clone(),
requested_module_type: requested_module_type.clone(),
},
specifier_key: None,
referrer_source_offset: None,
phase,
};
inner.root_module_reference = Some(module_request.reference.clone());
let load_fut = if phase == ModuleImportPhase::Evaluation
&& let Some(module_id) = inner.root_module_id
{
inner.register_and_recurse_inner(
module_id,
&module_request.reference,
None,
);
if inner.pending.is_empty() {
inner.state = LoadState::Done;
} else {
inner.state = LoadState::LoadingImports;
}
return Self::poll_next(Pin::new(inner), cx);
} else {
let loader = inner.loader.clone();
let is_dynamic_import = inner.is_dynamic_import();
let is_synchronous = inner.is_synchronous();
let requested_module_type = requested_module_type.clone();
async move {
let load_response = loader.load(
&module_specifier,
None,
ModuleLoadOptions {
is_dynamic_import,
is_synchronous,
requested_module_type,
},
);
let result = match load_response {
ModuleLoadResponse::Sync(result) => result,
ModuleLoadResponse::Async(fut) => fut.await,
};
result.map(|s| Some((module_request, s)))
}
.boxed_local()
};
inner.pending.push(load_fut);
inner.state = LoadState::LoadingRoot;
inner.try_poll_next_unpin(cx)
}
LoadState::LoadingRoot | LoadState::LoadingImports => {
match inner.pending.try_poll_next_unpin(cx)? {
Poll::Ready(None) => unreachable!(),
Poll::Ready(Some(None)) => {
if inner.pending.is_empty() {
inner.state = LoadState::Done;
Poll::Ready(None)
} else {
inner.try_poll_next_unpin(cx)
}
}
Poll::Ready(Some(Some(info))) => Poll::Ready(Some(Ok(info))),
Poll::Pending => Poll::Pending,
}
}
LoadState::Done => Poll::Ready(None),
}
}
}
fn source_mapped_module_load_referrer(
source_mapper: &RefCell<SourceMapper>,
referrer: &ModuleSpecifier,
code: &ModuleSourceCode,
source_offset: i32,
) -> Option<ModuleLoadReferrer> {
let (line_number, column_number) = code
.as_bytes()
.split_at_checked(source_offset as usize)?
.0
.iter()
.enumerate()
.filter(|(_, c)| **c as char == '\n')
.enumerate()
.last()
.map(|(n, (i, _))| (n as u32 + 2, source_offset as u32 - i as u32))
.unwrap_or_else(|| (1, source_offset as u32 + 1));
let (specifier, line_number, column_number) = match source_mapper
.borrow_mut()
.apply_source_map(referrer.as_str(), line_number, column_number)
{
SourceMapApplication::Unchanged => {
(referrer.clone(), line_number as _, column_number as _)
}
SourceMapApplication::LineAndColumn {
line_number,
column_number,
} => (referrer.clone(), line_number as _, column_number as _),
SourceMapApplication::LineAndColumnAndFileName {
file_name,
line_number,
column_number,
} => (
ModuleSpecifier::parse(&file_name).ok()?,
line_number as _,
column_number as _,
),
};
Some(ModuleLoadReferrer {
specifier,
line_number,
column_number,
})
}