@@ -35,6 +35,8 @@ use crate::modules::RequestedModuleType;
3535use crate :: modules:: ResolutionKind ;
3636use crate :: modules:: loaders:: ModuleLoadReferrer ;
3737use crate :: modules:: map:: ModuleMap ;
38+ use crate :: modules:: map:: NewModuleResult ;
39+ use crate :: modules:: map:: PendingModule ;
3840use crate :: modules:: module_map_data:: ModuleSourceKind ;
3941use crate :: source_map:: SourceMapApplication ;
4042use crate :: source_map:: SourceMapper ;
@@ -83,7 +85,7 @@ pub(crate) struct RecursiveModuleLoad {
8385 root_module_id : Option < ModuleId > ,
8486 init : LoadInit ,
8587 state : LoadState ,
86- module_map_rc : Rc < ModuleMap > ,
88+ pub ( crate ) module_map_rc : Rc < ModuleMap > ,
8789 pending : FuturesUnordered < Pin < Box < ModuleLoadFuture > > > ,
8890 /// Pending async root resolution future.
8991 pending_root_resolve : Option < Pin < Box < ModuleResolveFuture > > > ,
@@ -325,11 +327,20 @@ impl RecursiveModuleLoad {
325327 }
326328
327329 pub ( crate ) fn register_and_recurse (
330+ & mut self ,
331+ scope : & mut v8:: PinScope ,
332+ module_request : & ModuleRequest ,
333+ module_source : ModuleSource ,
334+ ) -> Result < RegisterOutcome , ModuleError > {
335+ self . register_and_recurse_impl ( scope, module_request, module_source)
336+ }
337+
338+ fn register_and_recurse_impl (
328339 & mut self ,
329340 scope : & mut v8:: PinScope ,
330341 module_request : & ModuleRequest ,
331342 mut module_source : ModuleSource ,
332- ) -> Result < ( ) , ModuleError > {
343+ ) -> Result < RegisterOutcome , ModuleError > {
333344 // Synthetic_esm specifiers carry a sentinel `ModuleSource` from
334345 // the load step. Divert to building the synthetic module from the
335346 // backing script's cached exports instead of going through
@@ -348,19 +359,8 @@ impl RecursiveModuleLoad {
348359 let exception = e. to_v8_error ( scope) ;
349360 ModuleError :: Exception ( v8:: Global :: new ( scope, exception) )
350361 } ) ?;
351- self . register_and_recurse_inner (
352- module_id,
353- & module_request. reference ,
354- None ,
355- ) ;
356- if self . state == LoadState :: LoadingRoot {
357- self . root_module_id = Some ( module_id) ;
358- self . state = LoadState :: LoadingImports ;
359- }
360- if self . pending . is_empty ( ) {
361- self . state = LoadState :: Done ;
362- }
363- return Ok ( ( ) ) ;
362+ self . finalize_module ( module_id, & module_request. reference , None ) ;
363+ return Ok ( RegisterOutcome :: Done ) ;
364364 }
365365
366366 if let Some ( source_kind) =
@@ -379,7 +379,7 @@ impl RecursiveModuleLoad {
379379 if self . pending . is_empty ( ) {
380380 self . state = LoadState :: Done ;
381381 }
382- return Ok ( ( ) ) ;
382+ return Ok ( RegisterOutcome :: Done ) ;
383383 }
384384 } else if module_request. phase == ModuleImportPhase :: Source {
385385 let message = v8:: String :: new (
@@ -396,28 +396,51 @@ impl RecursiveModuleLoad {
396396
397397 let code = module_source. cheap_copy_code ( ) ;
398398
399- let module_id = self . module_map_rc . new_module (
399+ match self . module_map_rc . new_module_with_pending (
400400 scope,
401401 self . is_currently_loading_main_module ( ) ,
402402 self . is_dynamic_import ( ) ,
403403 module_source,
404- ) ?;
404+ ) ? {
405+ NewModuleResult :: Ready ( module_id) => {
406+ self . finalize_module ( module_id, & module_request. reference , Some ( & code) ) ;
407+ Ok ( RegisterOutcome :: Done )
408+ }
409+ NewModuleResult :: Pending ( pending) => {
410+ Ok ( RegisterOutcome :: PendingFinalize {
411+ pending : Box :: new ( pending) ,
412+ reference : module_request. reference . clone ( ) ,
413+ code : Some ( code) ,
414+ } )
415+ }
416+ }
417+ }
405418
406- self . register_and_recurse_inner (
407- module_id,
408- & module_request. reference ,
409- Some ( & code) ,
410- ) ;
419+ /// Continue `register_and_recurse` after the caller has driven a
420+ /// [`PendingModule`] to completion via `ModuleMap::finalize_pending_module`.
421+ pub ( crate ) fn finalize_after_pending (
422+ & mut self ,
423+ module_id : ModuleId ,
424+ reference : & ModuleReference ,
425+ code : Option < & ModuleSourceCode > ,
426+ ) {
427+ self . finalize_module ( module_id, reference, code) ;
428+ }
411429
430+ fn finalize_module (
431+ & mut self ,
432+ module_id : ModuleId ,
433+ reference : & ModuleReference ,
434+ code : Option < & ModuleSourceCode > ,
435+ ) {
436+ self . register_and_recurse_inner ( module_id, reference, code) ;
412437 if self . state == LoadState :: LoadingRoot {
413438 self . root_module_id = Some ( module_id) ;
414439 self . state = LoadState :: LoadingImports ;
415440 }
416441 if self . pending . is_empty ( ) {
417442 self . state = LoadState :: Done ;
418443 }
419-
420- Ok ( ( ) )
421444 }
422445
423446 fn register_and_recurse_inner (
@@ -490,9 +513,6 @@ impl RecursiveModuleLoad {
490513 let is_synchronous = self . is_synchronous ( ) ;
491514 let requested_module_type =
492515 request. reference . requested_module_type . clone ( ) ;
493- let needs_resolve = request. needs_resolve ;
494- let raw_specifier = request. specifier_key . clone ( ) ;
495- let referrer_str = referrer. to_string ( ) ;
496516 let module_map_rc = self . module_map_rc . clone ( ) ;
497517 let fut = async move {
498518 // `visited_as_alias` unlike `visited` is checked as late as
@@ -506,49 +526,9 @@ impl RecursiveModuleLoad {
506526 return Ok ( None ) ;
507527 }
508528
509- // If the import needs async resolution, resolve it now.
510- let ( resolved_specifier, request) = if needs_resolve {
511- let raw = raw_specifier
512- . as_deref ( )
513- . unwrap_or ( request. reference . specifier . as_str ( ) ) ;
514- let kind = if is_dynamic_import {
515- ResolutionKind :: DynamicImport
516- } else {
517- ResolutionKind :: Import
518- } ;
519- let resolved = module_map_rc
520- . resolve_async ( raw, & referrer_str, kind)
521- . await
522- . map_err ( |e| JsErrorBox :: generic ( e. to_string ( ) ) ) ?;
523- // The async resolver may map the raw specifier to a URL
524- // that's already registered in the module map (e.g. a
525- // bare `"path"` from an npm package resolving to
526- // `node:path`, which was lazy-loaded earlier). In that
527- // case the load step would try `take_lazy_esm_source`,
528- // miss because the source was already consumed, and fall
529- // through to `loader.load(node:path)` which doesn't
530- // recognize the scheme. Short-circuit here so
531- // `register_and_recurse_inner` later picks up the
532- // existing module id via `get_id`.
533- if module_map_rc
534- . get_id (
535- resolved. as_str ( ) ,
536- & request. reference . requested_module_type ,
537- )
538- . is_some ( )
539- {
540- visited_as_alias
541- . borrow_mut ( )
542- . insert ( resolved. as_str ( ) . to_string ( ) ) ;
543- return Ok ( None ) ;
544- }
545- let mut resolved_request = request;
546- resolved_request. reference . specifier = resolved. clone ( ) ;
547- resolved_request. needs_resolve = false ;
548- ( resolved, resolved_request)
549- } else {
550- ( request. reference . specifier . clone ( ) , request)
551- } ;
529+ // Imports are pre-resolved in `new_module_from_js_source` --
530+ // `request.reference.specifier` already holds the final URL.
531+ let resolved_specifier = request. reference . specifier . clone ( ) ;
552532
553533 // First check if this module is a lazy-loaded ESM source
554534 // (embedded in binary but not snapshotted).
@@ -611,20 +591,93 @@ impl RecursiveModuleLoad {
611591 /// loaded module. Returns the root module ID once the full graph is loaded.
612592 pub ( crate ) async fn run_to_completion (
613593 mut self ,
614- mut on_module : impl FnMut (
594+ mut step : impl FnMut (
615595 & mut Self ,
616- & ModuleRequest ,
617- ModuleSource ,
618- ) -> Result < ( ) , CoreError > ,
596+ RegisterStep < ' _ > ,
597+ ) -> Result < RegisterOutcome , CoreError > ,
619598 ) -> Result < ModuleId , CoreError > {
620599 while let Some ( load_result) = self . next ( ) . await {
621600 let ( request, source) = load_result?;
622- on_module ( & mut self , & request, source) ?;
601+ match step (
602+ & mut self ,
603+ RegisterStep :: Register {
604+ request : & request,
605+ source,
606+ } ,
607+ ) ? {
608+ RegisterOutcome :: Done => { }
609+ RegisterOutcome :: PendingFinalize {
610+ pending,
611+ reference,
612+ code,
613+ } => {
614+ let module_map = self . module_map_rc . clone ( ) ;
615+ let module_id = module_map
616+ . finalize_pending_module ( * pending)
617+ . await
618+ . map_err ( |e| match e {
619+ ModuleError :: Core ( core) => core,
620+ // Concrete / Exception variants shouldn't surface from the
621+ // finalize step (the only failure mode here is the resolve
622+ // hook erroring), but map them defensively.
623+ other => CoreError :: from ( JsErrorBox :: generic ( format ! (
624+ "module finalize failed: {other:?}"
625+ ) ) ) ,
626+ } ) ?;
627+ step (
628+ & mut self ,
629+ RegisterStep :: Finalize {
630+ module_id,
631+ reference : & reference,
632+ code : code. as_ref ( ) ,
633+ } ,
634+ ) ?;
635+ }
636+ }
623637 }
624638 Ok ( self . root_module_id . expect ( "Root module should be loaded" ) )
625639 }
626640}
627641
642+ /// Action requested by [`RecursiveModuleLoad::run_to_completion`] of its
643+ /// driver. A single `FnMut` closure handles both — passing one closure rather
644+ /// than two side-steps the borrow checker (both phases need `&mut isolate`,
645+ /// but they're never active simultaneously).
646+ pub ( crate ) enum RegisterStep < ' a > {
647+ /// Compile a freshly-loaded module's source, resolve its imports, and
648+ /// register it. Returns [`RegisterOutcome`].
649+ Register {
650+ request : & ' a ModuleRequest ,
651+ source : ModuleSource ,
652+ } ,
653+ /// Continue after `ModuleMap::finalize_pending_module` resolved the
654+ /// async-resolved imports of a previous `Register` step. Must return
655+ /// `RegisterOutcome::Done`.
656+ Finalize {
657+ module_id : ModuleId ,
658+ reference : & ' a ModuleReference ,
659+ code : Option < & ' a ModuleSourceCode > ,
660+ } ,
661+ }
662+
663+ /// Outcome of a single `register_and_recurse` call. `PendingFinalize` lets the
664+ /// driver loop drop the V8 scope, await async resolves, then re-enter scope to
665+ /// complete registration -- without that detour we'd be stuck holding a scope
666+ /// across an `.await`.
667+ pub ( crate ) enum RegisterOutcome {
668+ /// Module was registered synchronously; no further work for this entry.
669+ Done ,
670+ /// Module compiled but had child imports whose resolution returned
671+ /// `ModuleResolveResponse::Async`. The driver awaits
672+ /// `ModuleMap::finalize_pending_module(pending)` and then calls the
673+ /// `on_finalize` continuation with the resulting [`ModuleId`].
674+ PendingFinalize {
675+ pending : Box < PendingModule > ,
676+ reference : ModuleReference ,
677+ code : Option < ModuleSourceCode > ,
678+ } ,
679+ }
680+
628681impl RecursiveModuleLoad {
629682 /// Shared logic for Init and ResolvingRoot states once the root specifier
630683 /// has been resolved.
@@ -647,7 +700,6 @@ impl RecursiveModuleLoad {
647700 specifier_key : None ,
648701 referrer_source_offset : None ,
649702 phase,
650- needs_resolve : false ,
651703 } ;
652704 inner. root_module_reference = Some ( module_request. reference . clone ( ) ) ;
653705 let load_fut = if phase == ModuleImportPhase :: Evaluation
0 commit comments