@@ -150,6 +150,7 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> {
150150 let expose_name = expose. path . trim_start_matches ( "./" ) . to_string ( ) ;
151151 StatsExpose {
152152 path : expose. path . clone ( ) ,
153+ file : String :: new ( ) ,
153154 id : compose_id_with_separator ( & container_name, & expose_name) ,
154155 name : expose_name,
155156 requires : Vec :: new ( ) ,
@@ -166,7 +167,8 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> {
166167 name : shared. name . clone ( ) ,
167168 version : shared. version . clone ( ) . unwrap_or_default ( ) ,
168169 requiredVersion : shared. required_version . clone ( ) ,
169- singleton : shared. singleton ,
170+ // default singleton to true when not provided by user
171+ singleton : shared. singleton . or ( Some ( true ) ) ,
170172 assets : StatsAssetsGroup :: default ( ) ,
171173 usedIn : Vec :: new ( ) ,
172174 } )
@@ -207,6 +209,7 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> {
207209 } ;
208210
209211 let mut exposes_map: HashMap < String , StatsExpose > = HashMap :: default ( ) ;
212+ let mut expose_chunk_names: HashMap < String , String > = HashMap :: default ( ) ;
210213 let mut shared_map: HashMap < String , StatsShared > = HashMap :: default ( ) ;
211214 let mut shared_usage_links: Vec < ( String , String ) > = Vec :: new ( ) ;
212215 let mut shared_module_targets: HashMap < String , HashSet < ModuleIdentifier > > = HashMap :: default ( ) ;
@@ -253,13 +256,21 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> {
253256 } ;
254257 let id_comp = compose_id_with_separator ( & container_name, & expose_name) ;
255258 let expose_file_key = strip_ext ( import) ;
256- exposes_map. entry ( expose_file_key) . or_insert ( StatsExpose {
257- path : expose_key. clone ( ) ,
258- id : id_comp,
259- name : expose_name,
260- requires : Vec :: new ( ) ,
261- assets : StatsAssetsGroup :: default ( ) ,
262- } ) ;
259+ exposes_map
260+ . entry ( expose_file_key. clone ( ) )
261+ . or_insert ( StatsExpose {
262+ path : expose_key. clone ( ) ,
263+ file : String :: new ( ) ,
264+ id : id_comp,
265+ name : expose_name,
266+ requires : Vec :: new ( ) ,
267+ assets : StatsAssetsGroup :: default ( ) ,
268+ } ) ;
269+ if let Some ( n) = & options. name
270+ && !n. is_empty ( )
271+ {
272+ expose_chunk_names. insert ( expose_file_key, n. clone ( ) ) ;
273+ }
263274 }
264275 continue ;
265276 }
@@ -277,6 +288,18 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> {
277288 if entry. version . is_empty ( ) {
278289 entry. version = ver;
279290 }
291+ // overlay user-configured shared options (singleton/requiredVersion/version)
292+ if let Some ( opt) = self . options . shared . iter ( ) . find ( |s| s. name == pkg) {
293+ if let Some ( singleton) = opt. singleton {
294+ entry. singleton = Some ( singleton) ;
295+ }
296+ if entry. requiredVersion . is_none ( ) {
297+ entry. requiredVersion = opt. required_version . clone ( ) ;
298+ }
299+ if let Some ( cfg_ver) = opt. version . clone ( ) . filter ( |_| entry. version . is_empty ( ) ) {
300+ entry. version = cfg_ver;
301+ }
302+ }
280303 let targets = shared_module_targets. entry ( pkg. clone ( ) ) . or_default ( ) ;
281304 for connection in module_graph. get_outgoing_connections ( & module_identifier) {
282305 let referenced = * connection. module_identifier ( ) ;
@@ -321,6 +344,19 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> {
321344 if entry. requiredVersion . is_none ( ) && required. is_some ( ) {
322345 entry. requiredVersion = required;
323346 }
347+ // overlay user-configured shared options
348+ if let Some ( opt) = self . options . shared . iter ( ) . find ( |s| s. name == pkg) {
349+ if let Some ( singleton) = opt. singleton {
350+ entry. singleton = Some ( singleton) ;
351+ }
352+ // prefer parsed requiredVersion but fill from config if still None
353+ if entry. requiredVersion . is_none ( ) {
354+ entry. requiredVersion = opt. required_version . clone ( ) ;
355+ }
356+ if let Some ( cfg_ver) = opt. version . clone ( ) . filter ( |_| entry. version . is_empty ( ) ) {
357+ entry. version = cfg_ver;
358+ }
359+ }
324360 record_shared_usage (
325361 & mut shared_usage_links,
326362 & pkg,
@@ -395,14 +431,34 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> {
395431 }
396432
397433 for ( expose_file_key, expose) in exposes_map. iter_mut ( ) {
398- let mut assets = if let Some ( module_id) = module_ids_by_name. get ( expose_file_key) {
399- collect_assets_for_module ( compilation, module_id, & entry_point_names)
400- . unwrap_or_else ( empty_assets_group)
401- } else if let Some ( chunk_key) = compilation. named_chunks . get ( expose_file_key) {
402- collect_assets_from_chunk ( compilation, chunk_key, & entry_point_names)
403- } else {
404- empty_assets_group ( )
405- } ;
434+ let mut assets = None ;
435+ if let Some ( chunk_name) = expose_chunk_names. get ( expose_file_key)
436+ && let Some ( chunk_key) = compilation. named_chunks . get ( chunk_name)
437+ {
438+ assets = Some ( collect_assets_from_chunk (
439+ compilation,
440+ chunk_key,
441+ & entry_point_names,
442+ ) ) ;
443+ }
444+ if assets. is_none ( )
445+ && let Some ( chunk_key) = compilation. named_chunks . get ( expose_file_key)
446+ {
447+ assets = Some ( collect_assets_from_chunk (
448+ compilation,
449+ chunk_key,
450+ & entry_point_names,
451+ ) ) ;
452+ }
453+ if assets. is_none ( )
454+ && let Some ( module_id) = module_ids_by_name. get ( expose_file_key)
455+ {
456+ assets = collect_assets_for_module ( compilation, module_id, & entry_point_names) ;
457+ }
458+ let mut assets = assets. unwrap_or_else ( empty_assets_group) ;
459+ if let Some ( path) = expose_module_paths. get ( expose_file_key) {
460+ expose. file = path. clone ( ) ;
461+ }
406462 if !entry_name. is_empty ( ) {
407463 assets. js . sync . retain ( |asset| asset != & entry_name) ;
408464 assets. js . r#async . retain ( |asset| asset != & entry_name) ;
@@ -441,6 +497,15 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> {
441497 . css
442498 . sync
443499 . retain ( |asset| !shared_asset_files. contains ( asset) ) ;
500+ if !entry_name. is_empty ( ) {
501+ entry_assets. js . sync . retain ( |asset| asset != & entry_name) ;
502+ entry_assets. js . r#async . retain ( |asset| asset != & entry_name) ;
503+ entry_assets. css . sync . retain ( |asset| asset != & entry_name) ;
504+ entry_assets
505+ . css
506+ . r#async
507+ . retain ( |asset| asset != & entry_name) ;
508+ }
444509 normalize_assets_group ( & mut entry_assets) ;
445510 for expose in exposes_map. values_mut ( ) {
446511 let is_empty = expose. assets . js . sync . is_empty ( )
@@ -487,7 +552,17 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> {
487552 ( None , alias. clone ( ) )
488553 } ;
489554 let used_in =
490- collect_usage_files_for_module ( compilation, module_graph, & module_id, & entry_point_names) ;
555+ collect_usage_files_for_module ( compilation, module_graph, & module_id, & entry_point_names)
556+ // keep only the file path, drop aggregated suffix like " + 1 modules"
557+ . into_iter ( )
558+ . map ( |s| {
559+ if let Some ( ( before, _) ) = s. split_once ( " + " ) {
560+ before. to_string ( )
561+ } else {
562+ s
563+ }
564+ } )
565+ . collect ( ) ;
491566 remote_list. push ( StatsRemote {
492567 alias : alias. clone ( ) ,
493568 consumingFederationContainerName : container_name. clone ( ) ,
@@ -509,6 +584,27 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> {
509584 . collect :: < Vec < _ > > ( ) ;
510585 ( exposes, shared, remote_list)
511586 } ;
587+ // Ensure all configured remotes exist in stats, add missing with defaults
588+ let mut remote_list = remote_list;
589+ for ( alias, target) in self . options . remote_alias_map . iter ( ) {
590+ if !remote_list. iter ( ) . any ( |r| r. alias == * alias) {
591+ let remote_container_name = if target. name . is_empty ( ) {
592+ alias. clone ( )
593+ } else {
594+ target. name . clone ( )
595+ } ;
596+ remote_list. push ( StatsRemote {
597+ alias : alias. clone ( ) ,
598+ consumingFederationContainerName : container_name. clone ( ) ,
599+ federationContainerName : remote_container_name. clone ( ) ,
600+ // default moduleName to "." for missing entries
601+ moduleName : "." . to_string ( ) ,
602+ entry : target. entry . clone ( ) ,
603+ usedIn : vec ! [ "UNKNOWN" . to_string( ) ] ,
604+ } ) ;
605+ }
606+ }
607+
512608 let stats_root = StatsRoot {
513609 id : container_name. clone ( ) ,
514610 name : container_name. clone ( ) ,
0 commit comments