/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
libs/core/modules/recursive_load.rs
794 строки
26 KB
Bartek Iwańczuk
fix(ext/node): import attributes and custom module types in registerHooks ESM hooks (#35028)
22 июн 2026, 15:19
Не верифицирован
22 июн 2026, 15:19
77f9001
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. 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::map::NewModuleResult; 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, } /// Describes the entrypoint of a recursive module load. #[derive(Debug)] enum LoadInit { /// Main module specifier. Main(String), /// Module specifier for side module. Side { specifier: String, kind: SideModuleKind, code: Option<String>, }, /// Dynamic import specifier with referrer and expected /// module type (which is determined by import assertion). DynamicImport(String, String, RequestedModuleType, ModuleImportPhase), } #[derive(Debug, Eq, PartialEq)] enum LoadState { Init, LoadingRoot, LoadingImports, Done, } /// This future is used to implement parallel async module loading. pub(crate) struct RecursiveModuleLoad { id: ModuleLoadId, root_module_id: Option<ModuleId>, init: LoadInit, state: LoadState, pub(crate) 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>>, // The loader is copied from `module_map_rc`, but its reference is cloned // ahead of time to avoid already-borrowed errors. loader: Rc<dyn ModuleLoader>, } impl Drop for RecursiveModuleLoad { fn drop(&mut self) { self.loader.finish_load(); } } impl RecursiveModuleLoad { /// Starts a new asynchronous load of the module graph for given specifier. /// /// The module corresponding for the given `specifier` will be marked as // "the main module" (`import.meta.main` will return `true` for this module). 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) } /// Starts a new asynchronous load of the module graph for given specifier. 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 new_dynamic_import( specifier: String, referrer: String, requested_module_type: RequestedModuleType, phase: ModuleImportPhase, module_map_rc: Rc<ModuleMap>, resolved_specifier: Result<ModuleSpecifier, ModuleLoaderError>, ) -> Self { Self::new_with_resolved_specifier( LoadInit::DynamicImport( specifier, referrer, requested_module_type, phase, ), module_map_rc, resolved_specifier, ) } fn new(init: LoadInit, module_map_rc: Rc<ModuleMap>) -> Self { let resolve_response = Self::resolve_root_from_init(&init, &module_map_rc); Self::new_with_resolved_specifier(init, module_map_rc, resolve_response) } fn new_with_resolved_specifier( init: LoadInit, module_map_rc: Rc<ModuleMap>, resolve_response: Result<ModuleSpecifier, ModuleLoaderError>, ) -> Self { let id = module_map_rc.next_load_id(); let loader = module_map_rc.loader.borrow().clone(); let resolved_specifier = Some(resolve_response.map_err(CoreError::from)); 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(), Self::requested_module_type_from_init(&init)) }); 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 requested_module_type_from_init(init: &LoadInit) -> &RequestedModuleType { match init { LoadInit::DynamicImport(_, _, module_type, _) => module_type, _ => &RequestedModuleType::None, } } fn resolve_root_from_init( init: &LoadInit, module_map_rc: &ModuleMap, ) -> Result<ModuleSpecifier, ModuleLoaderError> { 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)) => { // Put it back for poll_next to use later. self.resolved_specifier = Some(Ok(spec.clone())); spec } Some(Err(err)) => return Err(err), None => { unreachable!("root specifier is resolved in RecursiveModuleLoad::new") } }; 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, ), }; // Skip prepare_load for lazy-loaded ESM sources -- they are embedded // in the binary and do not need fetching/preparation. Same goes for // `synthetic_esm` modules: their exports come from a backing polyfill // script, not an external fetch. if self .module_map_rc .has_lazy_esm_source(module_specifier.as_str()) || self .module_map_rc .has_synthetic_esm_module(module_specifier.as_str()) && !self .loader .should_load_synthetic_esm(module_specifier.as_str()) { return Ok(()); } 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, module_source: ModuleSource, ) -> Result<RegisterOutcome, ModuleError> { self.register_and_recurse_impl(scope, module_request, module_source) } fn register_and_recurse_impl( &mut self, scope: &mut v8::PinScope, module_request: &ModuleRequest, mut module_source: ModuleSource, ) -> Result<RegisterOutcome, ModuleError> { // Synthetic_esm specifiers carry a sentinel `ModuleSource` from // the load step. Divert to building the synthetic module from the // backing script's cached exports instead of going through // `new_module`. if self .module_map_rc .has_synthetic_esm_module(module_request.reference.specifier.as_str()) { let module_id = self .module_map_rc .build_synthetic_esm_module( scope, module_request.reference.specifier.as_str(), ) .map_err(|e| { let exception = e.to_v8_error(scope); ModuleError::Exception(v8::Global::new(scope, exception)) })?; self.finalize_module(module_id, &module_request.reference, None); return Ok(RegisterOutcome::Done); } 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(RegisterOutcome::Done); } } 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(); match self.module_map_rc.new_module_with_pending( scope, self.is_currently_loading_main_module(), self.is_dynamic_import(), module_source, )? { NewModuleResult::Ready(module_id) => { // If the import statement used a custom `type` attribute that // doesn't correspond to a builtin module type // (e.g. `with { type: "x-css" }`) and the loaded module was // registered under a different requested type (e.g. JavaScript -> // `RequestedModuleType::None` for a `module.registerHooks()` load // hook that returned `format: "module"`), also register the module // under the import's requested type so subsequent lookups for the // same `(specifier, attribute)` pair resolve to it. let imported_type = &module_request.reference.requested_module_type; if matches!(imported_type, RequestedModuleType::Other(_)) && self .module_map_rc .get_id(module_request.reference.specifier.as_str(), imported_type) != Some(module_id) { self.module_map_rc.register_under_type( module_request.reference.specifier.clone().into(), imported_type, module_id, ); } self.finalize_module(module_id, &module_request.reference, Some(&code)); Ok(RegisterOutcome::Done) } } } fn finalize_module( &mut self, module_id: ModuleId, reference: &ModuleReference, code: Option<&ModuleSourceCode>, ) { self.register_and_recurse_inner(module_id, reference, 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; } } fn register_and_recurse_inner( &mut self, module_id: usize, module_reference: &ModuleReference, code: Option<&ModuleSourceCode>, ) { // Recurse the module's imports. There are two cases for each import: // 1. If the module is not in the module map, start a new load for it in // `self.pending`. The result of that load should eventually be passed to // this function for recursion. // 2. If the module is already in the module map, queue it up to be // recursed synchronously here. // This robustly ensures that the whole graph is in the module map before // `LoadState::Done` is set. 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() { // Use the module's canonical (post-redirect) URL as the referrer for // its child imports. After `new_module` aliases e.g. `npm:foo@^1` to // its `file:///.../node_modules/...` location, V8 records the module // under the file URL, and Deno's resolver requires that file URL to // resolve bare specifiers via the package's `package.json`. Using the // original specifier (e.g. `npm:foo@^1`) here strands intra-package // imports without resolution context. let canonical_specifier = self .module_map_rc .get_name_by_id(module_id) .and_then(|n| ModuleSpecifier::parse(&n).ok()) .unwrap_or_else(|| module_reference.specifier.clone()); let referrer = &canonical_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_info = 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 module_map_rc = self.module_map_rc.clone(); let fut = async move { // `visited_as_alias` unlike `visited` is checked as late as // possible because it can only be populated after completed // loads, meaning a duplicate load future may have already been // dispatched before we know it's a duplicate. if visited_as_alias .borrow() .contains(request.reference.specifier.as_str()) { return Ok(None); } // Imports are pre-resolved in `new_module_from_js_source` -- // `request.reference.specifier` already holds the final URL. let resolved_specifier = request.reference.specifier.clone(); // First check if this module is a lazy-loaded ESM source // (embedded in binary but not snapshotted). let load_result = if let Some(source_code) = module_map_rc .take_lazy_esm_source(resolved_specifier.as_str()) { Ok(ModuleSource::new( crate::ModuleType::JavaScript, ModuleSourceCode::String(source_code), &resolved_specifier, None, )) } else if module_map_rc .has_synthetic_esm_module(resolved_specifier.as_str()) && !loader .should_load_synthetic_esm(resolved_specifier.as_str()) { // Sentinel source. `register_and_recurse` detects the // synthetic_esm specifier and constructs the module from // its backing script's cached exports object instead of // calling `new_module` with this source. Ok(ModuleSource::new( crate::ModuleType::JavaScript, ModuleSourceCode::String("".to_string().into()), &resolved_specifier, None, )) } else { let load_response = loader.load( &resolved_specifier, referrer_info.as_ref(), ModuleLoadOptions { is_dynamic_import, is_synchronous, requested_module_type, }, ); 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); } } } } /// Drives the module load stream to completion, calling `on_module` for each /// loaded module. Returns the root module ID once the full graph is loaded. pub(crate) async fn run_to_completion( mut self, mut step: impl FnMut( &mut Self, RegisterStep<'_>, ) -> Result<RegisterOutcome, CoreError>, ) -> Result<ModuleId, CoreError> { while let Some(load_result) = self.next().await { let (request, source) = load_result?; match step( &mut self, RegisterStep::Register { request: &request, source, }, )? { RegisterOutcome::Done => {} } } Ok(self.root_module_id.expect("Root module should be loaded")) } } /// Action requested by [`RecursiveModuleLoad::run_to_completion`] of its /// driver. A single `FnMut` closure handles both — passing one closure rather /// than two side-steps the borrow checker (both phases need `&mut isolate`, /// but they're never active simultaneously). pub(crate) enum RegisterStep<'a> { /// Compile a freshly-loaded module's source, resolve its imports, and /// register it. Returns [`RegisterOutcome`]. Register { request: &'a ModuleRequest, source: ModuleSource, }, } /// Outcome of a single `register_and_recurse` call. pub(crate) enum RegisterOutcome { /// Module was registered synchronously; no further work for this entry. Done, } impl RecursiveModuleLoad { /// Shared logic for Init once the root specifier has been resolved. fn init_with_resolved_root( inner: &mut Self, module_specifier: ModuleSpecifier, cx: &mut Context, ) -> Poll<Option<Result<(ModuleRequest, ModuleSource), CoreError>>> { 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 { // If the inner future is already in the map, we might be done (assuming there are no pending // loads). 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; } // Internally re-poll using the new state to avoid spinning the event loop again. return Pin::new(inner).poll_next(cx); } else if inner .module_map_rc .has_synthetic_esm_module(module_specifier.as_str()) && !inner .loader .should_load_synthetic_esm(module_specifier.as_str()) { let module_request = module_request.clone(); let module_specifier = module_specifier.clone(); async move { Ok(Some(( module_request, ModuleSource::new( crate::ModuleType::JavaScript, ModuleSourceCode::String("".to_string().into()), &module_specifier, None, ), ))) } .boxed_local() } 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) } } 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(); // IMPORTANT: Do not borrow `inner.module_map_rc` here. It may not be // available. 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!("root specifier is resolved before polling"), }; Self::init_with_resolved_root(inner, module_specifier, cx) } LoadState::LoadingRoot | LoadState::LoadingImports => { // Poll the futures that load the source code of the modules match inner.pending.try_poll_next_unpin(cx)? { Poll::Ready(None) => unreachable!(), Poll::Ready(Some(None)) => { // The future resolves to None when loading an already visited redirect if inner.pending.is_empty() { inner.state = LoadState::Done; Poll::Ready(None) } else { // Force re-poll to make sure new ModuleLoadFuture's wakers are registered 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> { // 1-based. 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, }) }