Skip to main content

rspack_plugin_javascript/plugin/
module_concatenation_plugin.rs

1#![allow(clippy::only_used_in_recursion)]
2use std::{borrow::Cow, collections::VecDeque, sync::Arc};
3
4use rayon::prelude::*;
5use rspack_collections::{
6  Identifiable, IdentifierDashMap, IdentifierIndexSet, IdentifierMap, IdentifierSet,
7};
8use rspack_core::{
9  BoxDependency, BoxModule, Compilation, CompilationOptimizeChunkModules, DependencyId,
10  DependencyType, ExportProvided, ExportsInfoArtifact, ExtendedReferencedExport, GetTargetResult,
11  ImportedByDeferModulesArtifact, LibIdentOptions, Logger, ModuleGraph, ModuleGraphCacheArtifact,
12  ModuleGraphConnection, ModuleGraphModule, ModuleIdentifier, OptimizationBailoutItem, Plugin,
13  ProvidedExports, RuntimeCondition, RuntimeSpec, SideEffectsStateArtifact, SourceType,
14  concatenated_module::{
15    ConcatenatedInnerModule, ConcatenatedModule, RootModuleContext, is_esm_dep_like,
16  },
17  filter_runtime, get_cached_readable_identifier, get_target,
18  incremental::IncrementalPasses,
19};
20use rspack_error::{Result, ToStringResultToRspackResultExt};
21use rspack_hook::{plugin, plugin_hook};
22use rspack_util::itoa;
23use rustc_hash::FxHashMap as HashMap;
24
25fn format_bailout_reason(msg: &str) -> String {
26  format!("ModuleConcatenation bailout: {msg}")
27}
28
29#[derive(Clone, Debug)]
30enum Warning {
31  Id(ModuleIdentifier),
32  Problem(String),
33}
34
35#[derive(Debug, Clone)]
36pub struct ConcatConfiguration {
37  pub root_module: ModuleIdentifier,
38  runtime: Option<RuntimeSpec>,
39  modules: IdentifierIndexSet,
40  warnings: IdentifierMap<Warning>,
41}
42
43impl ConcatConfiguration {
44  pub fn new(root_module: ModuleIdentifier, runtime: Option<RuntimeSpec>) -> Self {
45    let mut modules = IdentifierIndexSet::default();
46    modules.insert(root_module);
47
48    ConcatConfiguration {
49      root_module,
50      runtime,
51      modules,
52      warnings: IdentifierMap::default(),
53    }
54  }
55
56  fn add(&mut self, module: ModuleIdentifier) {
57    self.modules.insert(module);
58  }
59
60  fn has(&self, module: &ModuleIdentifier) -> bool {
61    self.modules.contains(module)
62  }
63
64  fn is_empty(&self) -> bool {
65    self.modules.len() == 1
66  }
67
68  fn add_warning(&mut self, module: ModuleIdentifier, problem: Warning) {
69    self.warnings.insert(module, problem);
70  }
71
72  fn get_warnings_sorted(&self) -> Vec<(ModuleIdentifier, Warning)> {
73    let mut sorted_warnings: Vec<_> = self.warnings.clone().into_iter().collect();
74    sorted_warnings.sort_by_key(|(id, _)| *id);
75    sorted_warnings
76  }
77
78  fn get_modules(&self) -> &IdentifierIndexSet {
79    &self.modules
80  }
81
82  fn snapshot(&self) -> usize {
83    self.modules.len()
84  }
85
86  fn rollback(&mut self, snapshot: usize) {
87    let modules = &mut self.modules;
88    let len = modules.len();
89    for _ in snapshot..len {
90      modules.pop();
91    }
92  }
93}
94
95#[plugin]
96#[derive(Debug, Default)]
97pub struct ModuleConcatenationPlugin {
98  bailout_reason_map: IdentifierDashMap<Arc<Cow<'static, str>>>,
99}
100
101#[derive(Default)]
102pub struct RuntimeIdentifierCache<T> {
103  no_runtime_map: IdentifierMap<T>,
104  runtime_map: HashMap<RuntimeSpec, IdentifierMap<T>>,
105}
106
107struct ModuleGraphArtifacts<'a> {
108  mg_cache: &'a ModuleGraphCacheArtifact,
109  side_effects_state_artifact: &'a SideEffectsStateArtifact,
110  exports_info_artifact: &'a ExportsInfoArtifact,
111}
112
113impl<T> RuntimeIdentifierCache<T> {
114  fn insert(&mut self, module: ModuleIdentifier, runtime: Option<&RuntimeSpec>, value: T) {
115    if let Some(runtime) = runtime {
116      if let Some(map) = self.runtime_map.get_mut(runtime) {
117        map.insert(module, value);
118      } else {
119        let mut map = IdentifierMap::with_capacity_and_hasher(1, Default::default());
120        map.insert(module, value);
121        self.runtime_map.insert(runtime.clone(), map);
122      }
123    } else {
124      self.no_runtime_map.insert(module, value);
125    }
126  }
127
128  fn get(&self, module: &ModuleIdentifier, runtime: Option<&RuntimeSpec>) -> Option<&T> {
129    if let Some(runtime) = runtime {
130      let map = self.runtime_map.get(runtime)?;
131
132      map.get(module)
133    } else {
134      self.no_runtime_map.get(module)
135    }
136  }
137}
138
139impl ModuleConcatenationPlugin {
140  fn format_bailout_warning(&self, module: ModuleIdentifier, warning: &Warning) -> String {
141    match warning {
142      Warning::Problem(id) => format_bailout_reason(&format!("Cannot concat with {module}: {id}")),
143      Warning::Id(id) => {
144        let reason = self.get_inner_bailout_reason(id);
145        let reason_with_prefix = match reason {
146          Some(reason) => format!(": {}", *reason),
147          None => String::new(),
148        };
149        if id == &module {
150          format_bailout_reason(&format!("Cannot concat with {module}{reason_with_prefix}"))
151        } else {
152          format_bailout_reason(&format!(
153            "Cannot concat with {module} because of {id}{reason_with_prefix}"
154          ))
155        }
156      }
157    }
158  }
159
160  fn set_bailout_reason(
161    &self,
162    module: &ModuleIdentifier,
163    reason: Cow<'static, str>,
164    mg: &mut ModuleGraph,
165  ) {
166    self.set_inner_bailout_reason(module, reason.clone());
167    mg.get_optimization_bailout_mut(module)
168      .push(OptimizationBailoutItem::Message(format_bailout_reason(
169        &reason,
170      )));
171  }
172
173  fn set_inner_bailout_reason(&self, module: &ModuleIdentifier, reason: Cow<'static, str>) {
174    self.bailout_reason_map.insert(*module, Arc::new(reason));
175  }
176
177  fn get_inner_bailout_reason(
178    &self,
179    module_id: &ModuleIdentifier,
180  ) -> Option<Arc<Cow<'static, str>>> {
181    self
182      .bailout_reason_map
183      .get(module_id)
184      .map(|reason| reason.clone())
185  }
186
187  fn get_imports(
188    mg: &ModuleGraph,
189    artifacts: &ModuleGraphArtifacts,
190    mi: ModuleIdentifier,
191    runtime: Option<&RuntimeSpec>,
192    imports_cache: &mut RuntimeIdentifierCache<IdentifierIndexSet>,
193    module_cache: &IdentifierMap<NoRuntimeModuleCache>,
194  ) -> IdentifierIndexSet {
195    if let Some(set) = imports_cache.get(&mi, runtime) {
196      return set.clone();
197    }
198
199    let cached = module_cache.get(&mi).expect("should have module");
200
201    let mut set =
202      IdentifierIndexSet::with_capacity_and_hasher(cached.connections.len(), Default::default());
203    for (con, (has_imported_names, cached_active)) in &cached.connections {
204      if set.contains(con.module_identifier()) {
205        continue;
206      }
207
208      let is_target_active = if let Some(runtime) = runtime {
209        if cached.runtime == *runtime {
210          // runtime is same, use cached value
211          *cached_active
212        } else if *cached_active && cached.runtime.is_subset(runtime) {
213          // cached runtime is subset and active, means it is also active in current runtime
214          true
215        } else if !*cached_active && cached.runtime.is_superset(runtime) {
216          // cached runtime is superset and inactive, means it is also inactive in current runtime
217          false
218        } else {
219          // can't determine, need to check
220          con.is_target_active(
221            mg,
222            Some(runtime),
223            artifacts.mg_cache,
224            artifacts.side_effects_state_artifact,
225            artifacts.exports_info_artifact,
226          )
227        }
228      } else {
229        // no runtime, need to check
230        con.is_target_active(
231          mg,
232          None,
233          artifacts.mg_cache,
234          artifacts.side_effects_state_artifact,
235          artifacts.exports_info_artifact,
236        )
237      };
238
239      if !is_target_active {
240        continue;
241      }
242      if *has_imported_names || cached.provided_names {
243        set.insert(*con.module_identifier());
244      }
245    }
246
247    imports_cache.insert(mi, runtime, set.clone());
248    set
249  }
250
251  #[allow(clippy::too_many_arguments)]
252  fn try_to_add(
253    compilation: &Compilation,
254    config: &mut ConcatConfiguration,
255    module_id: &ModuleIdentifier,
256    runtime: Option<&RuntimeSpec>,
257    active_runtime: Option<&RuntimeSpec>,
258    possible_modules: &IdentifierSet,
259    candidates: &mut IdentifierSet,
260    failure_cache: &mut IdentifierMap<Warning>,
261    success_cache: &mut RuntimeIdentifierCache<Vec<ModuleIdentifier>>,
262    avoid_mutate_on_failure: bool,
263    statistics: &mut Statistics,
264    imports_cache: &mut RuntimeIdentifierCache<IdentifierIndexSet>,
265    module_cache: &IdentifierMap<NoRuntimeModuleCache>,
266  ) -> Option<Warning> {
267    statistics
268      .module_visit
269      .entry(*module_id)
270      .and_modify(|count| {
271        *count += 1;
272      })
273      .or_insert(1);
274
275    if let Some(cache_entry) = failure_cache.get(module_id) {
276      statistics.cached += 1;
277      return Some(cache_entry.clone());
278    }
279
280    if config.has(module_id) {
281      statistics.already_in_config += 1;
282      return None;
283    }
284
285    let chunk_graph = &compilation.build_chunk_graph_artifact.chunk_graph;
286    let chunk_by_ukey = &compilation.build_chunk_graph_artifact.chunk_by_ukey;
287    let module_graph = compilation.get_module_graph();
288    let module_graph_cache = &compilation.module_graph_cache_artifact;
289    let side_effects_state_artifact = &compilation
290      .build_module_graph_artifact
291      .side_effects_state_artifact;
292    let module_graph_artifacts = ModuleGraphArtifacts {
293      mg_cache: module_graph_cache,
294      side_effects_state_artifact,
295      exports_info_artifact: &compilation.exports_info_artifact,
296    };
297
298    let incoming_modules = if let Some(incomings) = success_cache.get(module_id, runtime) {
299      statistics.cache_hit += 1;
300      incomings.clone()
301    } else {
302      let module_readable_identifier = get_cached_readable_identifier(
303        module_id,
304        module_graph,
305        &compilation.module_static_cache,
306        &compilation.options.context,
307      );
308
309      if !possible_modules.contains(module_id) {
310        statistics.invalid_module += 1;
311        let problem = Warning::Id(*module_id);
312        failure_cache.insert(*module_id, problem.clone());
313        return Some(problem);
314      }
315
316      let missing_chunks: Vec<_> = chunk_graph
317        .get_module_chunks(config.root_module)
318        .iter()
319        .filter(|chunk| !chunk_graph.is_module_in_chunk(module_id, **chunk))
320        .collect();
321
322      if !missing_chunks.is_empty() {
323        let problem_string = {
324          let mut missing_chunks_list = missing_chunks
325            .iter()
326            .map(|&chunk| {
327              let chunk = chunk_by_ukey.expect_get(chunk);
328              chunk.name().unwrap_or("unnamed chunk(s)")
329            })
330            .collect::<Vec<_>>();
331          missing_chunks_list.sort_unstable();
332
333          let mut chunks = chunk_graph
334            .get_module_chunks(*module_id)
335            .iter()
336            .map(|&chunk| {
337              let chunk = chunk_by_ukey.expect_get(&chunk);
338              chunk.name().unwrap_or("unnamed chunk(s)")
339            })
340            .collect::<Vec<_>>();
341          chunks.sort_unstable();
342
343          format!(
344            "Module {} is not in the same chunk(s) (expected in chunk(s) {}, module is in chunk(s) {})",
345            module_readable_identifier,
346            missing_chunks_list.join(", "),
347            chunks.join(", ")
348          )
349        };
350
351        statistics.incorrect_chunks += 1;
352        let problem = Warning::Problem(problem_string);
353        failure_cache.insert(*module_id, problem.clone());
354        return Some(problem);
355      }
356
357      let NoRuntimeModuleCache {
358        incomings,
359        active_incomings,
360        runtime: cached_module_runtime,
361        ..
362      } = module_cache
363        .get(module_id)
364        .expect("should have module cache");
365
366      if !incomings.from_non_modules.is_empty() {
367        let has_active_non_modules_connections =
368          incomings.from_non_modules.iter().any(|connection| {
369            is_connection_active_in_runtime(
370              connection,
371              runtime,
372              active_incomings,
373              cached_module_runtime,
374              module_graph,
375              &module_graph_artifacts,
376            )
377          });
378
379        // TODO: ADD module connection explanations
380        if has_active_non_modules_connections {
381          let problem = {
382            // let importing_explanations = active_non_modules_connections
383            //   .iter()
384            //   .flat_map(|&c| c.explanation())
385            //   .collect::<HashSet<_>>();
386            // let mut explanations: Vec<_> = importing_explanations.into_iter().collect();
387            // explanations.sort();
388            format!(
389              "Module {module_readable_identifier} is referenced",
390              // if !explanations.is_empty() {
391              //   format!("by: {}", explanations.join(", "))
392              // } else {
393              //   "in an unsupported way".to_string()
394              // }
395            )
396          };
397          let problem = Warning::Problem(problem);
398          statistics.incorrect_dependency += 1;
399          failure_cache.insert(*module_id, problem.clone());
400          return Some(problem);
401        }
402      }
403
404      let mut incoming_connections_from_modules =
405        IdentifierMap::with_capacity_and_hasher(incomings.from_modules.len(), Default::default());
406      for (origin_module, connections) in incomings.from_modules.iter() {
407        let number_of_chunks = module_cache.get(origin_module).map_or_else(
408          || chunk_graph.get_number_of_module_chunks(*origin_module),
409          |m| m.number_of_chunks,
410        );
411
412        if number_of_chunks == 0 {
413          // Ignore connection from orphan modules
414          continue;
415        }
416
417        let is_intersect = if let Some(runtime) = runtime {
418          if let Some(origin_runtime) = module_cache.get(origin_module).map(|m| &m.runtime) {
419            !runtime.is_disjoint(origin_runtime)
420          } else {
421            let origin_runtime = RuntimeSpec::from_runtimes(
422              chunk_graph.get_module_runtimes_iter(*origin_module, chunk_by_ukey),
423            );
424            !runtime.is_disjoint(&origin_runtime)
425          }
426        } else {
427          false
428        };
429
430        if !is_intersect {
431          continue;
432        }
433
434        let active_connections: Vec<_> = connections
435          .iter()
436          .filter(|&connection| {
437            is_connection_active_in_runtime(
438              connection,
439              runtime,
440              active_incomings,
441              cached_module_runtime,
442              module_graph,
443              &module_graph_artifacts,
444            )
445          })
446          .collect();
447
448        if !active_connections.is_empty() {
449          incoming_connections_from_modules.insert(*origin_module, active_connections);
450        }
451      }
452
453      let mut incoming_modules = incoming_connections_from_modules
454        .keys()
455        .copied()
456        .collect::<Vec<_>>();
457      let other_chunk_modules = incoming_modules
458        .iter()
459        .filter(|&origin_module| {
460          chunk_graph
461            .get_module_chunks(config.root_module)
462            .iter()
463            .any(|&chunk_ukey| !chunk_graph.is_module_in_chunk(origin_module, chunk_ukey))
464        })
465        .collect::<Vec<_>>();
466
467      if !other_chunk_modules.is_empty() {
468        let problem = {
469          let mut names: Vec<_> = other_chunk_modules
470            .into_iter()
471            .map(|mid| {
472              get_cached_readable_identifier(
473                mid,
474                module_graph,
475                &compilation.module_static_cache,
476                &compilation.options.context,
477              )
478            })
479            .collect();
480          names.sort();
481          format!(
482            "Module {} is referenced from different chunks by these modules: {}",
483            module_readable_identifier,
484            names.join(", ")
485          )
486        };
487
488        statistics.incorrect_chunks_of_importer += 1;
489        let problem = Warning::Problem(problem);
490        failure_cache.insert(*module_id, problem.clone());
491        return Some(problem);
492      }
493
494      let mut non_esm_connections = IdentifierMap::with_capacity_and_hasher(
495        incoming_connections_from_modules.len(),
496        Default::default(),
497      );
498      for (origin_module, connections) in incoming_connections_from_modules.iter() {
499        let has_non_esm_connections = connections.iter().any(|connection| {
500          let dep = module_graph.dependency_by_id(&connection.dependency_id);
501          !is_esm_dep_like(dep)
502        });
503
504        if has_non_esm_connections {
505          non_esm_connections.insert(*origin_module, connections);
506        }
507      }
508
509      if !non_esm_connections.is_empty() {
510        let problem = {
511          let names: Vec<_> = non_esm_connections
512            .iter()
513            .map(|(origin_module, connections)| {
514              let readable_identifier = get_cached_readable_identifier(
515                origin_module,
516                module_graph,
517                &compilation.module_static_cache,
518                &compilation.options.context,
519              );
520              let mut names = connections
521                .iter()
522                .map(|item| {
523                  let dep = module_graph.dependency_by_id(&item.dependency_id);
524                  dep.dependency_type().to_string()
525                })
526                .collect::<Vec<_>>();
527              names.sort();
528              format!(
529                "{} (referenced with {})",
530                readable_identifier,
531                names.join(",")
532              )
533            })
534            .collect();
535
536          format!(
537            "Module {} is referenced from these modules with unsupported syntax: {}",
538            module_readable_identifier,
539            names.join(", ")
540          )
541        };
542        let problem = Warning::Problem(problem);
543        statistics.incorrect_module_dependency += 1;
544        failure_cache.insert(*module_id, problem.clone());
545        return Some(problem);
546      }
547
548      if let Some(runtime) = runtime
549        && runtime.len() > 1
550      {
551        let mut other_runtime_connections = Vec::new();
552        'outer: for (origin_module, connections) in incoming_connections_from_modules {
553          let mut current_runtime_condition = RuntimeCondition::Boolean(false);
554          for connection in connections {
555            let runtime_condition = filter_runtime(Some(runtime), |runtime| {
556              connection.is_target_active(
557                module_graph,
558                runtime,
559                module_graph_cache,
560                &compilation
561                  .build_module_graph_artifact
562                  .side_effects_state_artifact,
563                &compilation.exports_info_artifact,
564              )
565            });
566
567            if runtime_condition == RuntimeCondition::Boolean(false) {
568              continue;
569            }
570
571            if runtime_condition == RuntimeCondition::Boolean(true) {
572              continue 'outer;
573            }
574
575            // here two runtime_condition must be `RuntimeCondition::Spec`
576            if current_runtime_condition != RuntimeCondition::Boolean(false) {
577              current_runtime_condition
578                .as_spec_mut()
579                .expect("should be spec")
580                .extend(runtime_condition.as_spec().expect("should be spec"));
581            } else {
582              current_runtime_condition = runtime_condition;
583            }
584          }
585
586          if current_runtime_condition != RuntimeCondition::Boolean(false) {
587            other_runtime_connections.push((origin_module, current_runtime_condition));
588          }
589        }
590
591        if !other_runtime_connections.is_empty() {
592          let problem = {
593            format!(
594              "Module {} is runtime-dependent referenced by these modules: {}",
595              module_readable_identifier,
596              other_runtime_connections
597                .iter()
598                .map(|(origin_module, runtime_condition)| {
599                  let readable_identifier = get_cached_readable_identifier(
600                    origin_module,
601                    module_graph,
602                    &compilation.module_static_cache,
603                    &compilation.options.context,
604                  );
605                  format!(
606                    "{} (expected runtime {}, module is only referenced in {})",
607                    readable_identifier,
608                    runtime,
609                    runtime_condition.as_spec().expect("should be spec")
610                  )
611                })
612                .collect::<Vec<_>>()
613                .join(", ")
614            )
615          };
616
617          let problem = Warning::Problem(problem);
618          statistics.incorrect_runtime_condition += 1;
619          failure_cache.insert(*module_id, problem.clone());
620          return Some(problem);
621        }
622      }
623
624      incoming_modules.sort();
625      success_cache.insert(*module_id, runtime, incoming_modules.clone());
626      incoming_modules
627    };
628
629    let backup = if avoid_mutate_on_failure {
630      Some(config.snapshot())
631    } else {
632      None
633    };
634
635    config.add(*module_id);
636
637    for origin_module in &incoming_modules {
638      if let Some(problem) = Self::try_to_add(
639        compilation,
640        config,
641        origin_module,
642        runtime,
643        active_runtime,
644        possible_modules,
645        candidates,
646        failure_cache,
647        success_cache,
648        false,
649        statistics,
650        imports_cache,
651        module_cache,
652      ) {
653        if let Some(backup) = &backup {
654          config.rollback(*backup);
655        }
656        statistics.importer_failed += 1;
657        failure_cache.insert(*module_id, problem.clone());
658        return Some(problem);
659      }
660    }
661
662    for imp in Self::get_imports(
663      module_graph,
664      &module_graph_artifacts,
665      *module_id,
666      runtime,
667      imports_cache,
668      module_cache,
669    ) {
670      candidates.insert(imp);
671    }
672    statistics.added += 1;
673    None
674  }
675
676  async fn optimize_chunk_modules_impl(&self, compilation: &mut Compilation) -> Result<()> {
677    let logger = compilation.get_logger("rspack.ModuleConcatenationPlugin");
678
679    if compilation.options.experiments.defer_import {
680      let mut imported_by_defer_modules_artifact = ImportedByDeferModulesArtifact::default();
681      let module_graph = compilation.get_module_graph();
682      for (_, dep) in module_graph.dependencies() {
683        if dep.get_phase().is_defer()
684          && matches!(
685            dep.dependency_type(),
686            DependencyType::EsmImport | DependencyType::EsmExportImport
687          )
688          && let Some(module) = module_graph.module_identifier_by_dependency_id(dep.id())
689        {
690          imported_by_defer_modules_artifact.insert(*module);
691        }
692      }
693      compilation.imported_by_defer_modules_artifact = imported_by_defer_modules_artifact.into();
694    }
695
696    let mut relevant_modules = vec![];
697    let mut possible_inners = IdentifierSet::default();
698    let start = logger.time("select relevant modules");
699    let module_graph = compilation.get_module_graph();
700
701    // filter modules that can be root
702    let modules: Vec<_> = module_graph
703      .module_graph_modules()
704      .map(|(k, _)| *k)
705      .collect();
706    let res: Vec<_> = modules
707      .into_par_iter()
708      .map(|module_id| {
709        let mut can_be_root = true;
710        let mut can_be_inner = true;
711        let mut bailout_reason = vec![];
712        let number_of_module_chunks = compilation
713          .build_chunk_graph_artifact
714          .chunk_graph
715          .get_number_of_module_chunks(module_id);
716        let is_entry_module = compilation
717          .build_chunk_graph_artifact
718          .chunk_graph
719          .is_entry_module(&module_id);
720        let module_graph = compilation.get_module_graph();
721        let m = module_graph
722          .module_by_identifier(&module_id)
723          .expect("should have module");
724
725        if let Some(reason) = m.get_concatenation_bailout_reason(
726          module_graph,
727          &compilation.build_chunk_graph_artifact.chunk_graph,
728        ) {
729          bailout_reason.push(reason);
730          return (false, false, module_id, bailout_reason);
731        }
732
733        if ModuleGraph::is_async(&compilation.async_modules_artifact, &module_id) {
734          bailout_reason.push("Module is async".into());
735          return (false, false, module_id, bailout_reason);
736        }
737
738        if !m.build_info().strict {
739          bailout_reason.push("Module is not in strict mode".into());
740          return (false, false, module_id, bailout_reason);
741        }
742        if number_of_module_chunks == 0 {
743          bailout_reason.push("Module is not in any chunk".into());
744          return (false, false, module_id, bailout_reason);
745        }
746
747        let exports_info = compilation
748          .exports_info_artifact
749          .get_exports_info_data(&module_id);
750        let relevant_exports = exports_info.get_relevant_exports(None);
751        let unknown_exports = relevant_exports
752          .iter()
753          .filter(|export_info| {
754            export_info.is_reexport()
755              && !matches!(
756                get_target(
757                  export_info,
758                  module_graph,
759                  &compilation.exports_info_artifact,
760                  &|_| true,
761                  &mut Default::default()
762                ),
763                Some(GetTargetResult::Target(_))
764              )
765          })
766          .copied()
767          .collect::<Vec<_>>();
768        if !unknown_exports.is_empty() {
769          let cur_bailout_reason = unknown_exports
770            .into_iter()
771            .map(|export_info| {
772              let name = export_info
773                .name()
774                .map_or("other exports".to_string(), |name| name.to_string());
775              format!("{} : {}", name, export_info.get_used_info())
776            })
777            .collect::<Vec<String>>()
778            .join(", ");
779          // self.set_bailout_reason(
780          //   &module_id,
781          //   format!("Reexports in this module do not have a static target ({bailout_reason})"),
782          //   &mut module_graph,
783          // );
784
785          bailout_reason.push(
786            format!("Reexports in this module do not have a static target ({cur_bailout_reason})")
787              .into(),
788          );
789
790          return (false, false, module_id, bailout_reason);
791        }
792        let unknown_provided_exports = relevant_exports
793          .iter()
794          .filter(|export_info| !matches!(export_info.provided(), Some(ExportProvided::Provided)))
795          .copied()
796          .collect::<Vec<_>>();
797
798        if !unknown_provided_exports.is_empty() {
799          let cur_bailout_reason = unknown_provided_exports
800            .into_iter()
801            .map(|export_info| {
802              let name = export_info
803                .name()
804                .map_or("other exports".to_string(), |name| name.to_string());
805              format!(
806                "{} : {} and {}",
807                name,
808                export_info.get_provided_info(),
809                export_info.get_used_info(),
810              )
811            })
812            .collect::<Vec<String>>()
813            .join(", ");
814          // self.set_bailout_reason(
815          //   &module_id,
816          //   format!("List of module exports is dynamic ({bailout_reason})"),
817          //   &mut module_graph,
818          // );
819          bailout_reason
820            .push(format!("List of module exports is dynamic ({cur_bailout_reason})").into());
821          can_be_root = false;
822        }
823
824        if is_entry_module {
825          // self.set_bailout_reason(
826          //   &module_id,
827          //   "Module is an entry point".to_string(),
828          //   &mut module_graph,
829          // );
830          can_be_inner = false;
831          bailout_reason.push("Module is an entry point".into());
832        }
833
834        if module_graph.is_deferred(&compilation.imported_by_defer_modules_artifact, &module_id) {
835          bailout_reason.push("Module is deferred".into());
836          can_be_inner = false;
837        }
838
839        (can_be_root, can_be_inner, module_id, bailout_reason)
840        // if can_be_root {
841        //   relevant_modules.push(module_id);
842        // }
843        // if can_be_inner {
844        //   possible_inners.insert(module_id);
845        // }
846      })
847      .collect();
848
849    let module_graph = compilation.get_module_graph_mut();
850
851    for (can_be_root, can_be_inner, module_id, bailout_reason) in res {
852      if can_be_root {
853        relevant_modules.push(module_id);
854      }
855      if can_be_inner {
856        possible_inners.insert(module_id);
857      }
858      for bailout_reason in bailout_reason {
859        self.set_bailout_reason(&module_id, bailout_reason, module_graph);
860      }
861    }
862
863    let module_graph = compilation.get_module_graph();
864    logger.time_end(start);
865    let mut relevant_len_buffer = itoa::Buffer::new();
866    let relevant_len_str = relevant_len_buffer.format(relevant_modules.len());
867    let mut possible_len_buffer = itoa::Buffer::new();
868    let possible_len_str = possible_len_buffer.format(possible_inners.len());
869    logger.debug(format!(
870      "{relevant_len_str} potential root modules, {possible_len_str} potential inner modules",
871    ));
872
873    let start = logger.time("sort relevant modules");
874    relevant_modules.sort_by(|a, b| {
875      let ad = module_graph.get_depth(a);
876      let bd = module_graph.get_depth(b);
877      ad.cmp(&bd)
878    });
879
880    logger.time_end(start);
881    let mut statistics = Statistics::default();
882    let mut stats_candidates = 0;
883    let mut stats_size_sum = 0;
884    let mut stats_empty_configurations = 0;
885
886    let start = logger.time("find modules to concatenate");
887    let mut concat_configurations: Vec<ConcatConfiguration> = Vec::new();
888    let mut used_as_inner: IdentifierSet = IdentifierSet::default();
889    let mut imports_cache = RuntimeIdentifierCache::<IdentifierIndexSet>::default();
890
891    let module_graph = compilation.get_module_graph();
892    let module_graph_cache = &compilation.module_graph_cache_artifact;
893    let module_static_cache = &compilation.module_static_cache;
894    let compilation_context = &compilation.options.context;
895    let cache_modules = relevant_modules
896      .iter()
897      .chain(possible_inners.iter())
898      .copied()
899      .collect::<IdentifierSet>();
900    let modules_without_runtime_cache_entries = cache_modules
901      .into_par_iter()
902      .map(|module_id| {
903        let exports_info = compilation
904          .exports_info_artifact
905          .get_exports_info_data(&module_id);
906        let provided_names = matches!(
907          exports_info.get_provided_exports(),
908          ProvidedExports::ProvidedNames(_)
909        );
910        let module = module_graph
911          .module_by_identifier(&module_id)
912          .expect("should have module");
913        let runtime = RuntimeSpec::from_runtimes(
914          compilation
915            .build_chunk_graph_artifact
916            .chunk_graph
917            .get_module_runtimes_iter(
918              module_id,
919              &compilation.build_chunk_graph_artifact.chunk_by_ukey,
920            ),
921        );
922
923        let _ = get_cached_readable_identifier(
924          &module_id,
925          module_graph,
926          module_static_cache,
927          compilation_context,
928        );
929
930        let connections = module
931          .get_dependencies()
932          .iter()
933          .filter_map(|d| {
934            let dep = module_graph.dependency_by_id(d);
935            if !is_esm_dep_like(dep) {
936              return None;
937            }
938            let con = module_graph.connection_by_dependency_id(d)?;
939            let module_dep = dep.as_module_dependency().expect("should be module dep");
940            let imported_names = module_dep.get_referenced_exports(
941              module_graph,
942              module_graph_cache,
943              &compilation.exports_info_artifact,
944              None,
945            );
946
947            Some((
948              con.clone(),
949              (
950                imported_names.iter().all(|item| match item {
951                  ExtendedReferencedExport::Array(arr) => !arr.is_empty(),
952                  ExtendedReferencedExport::Export(export) => !export.name.is_empty(),
953                }),
954                con.is_target_active(
955                  module_graph,
956                  Some(&runtime),
957                  module_graph_cache,
958                  &compilation
959                    .build_module_graph_artifact
960                    .side_effects_state_artifact,
961                  &compilation.exports_info_artifact,
962                ),
963              ),
964            ))
965          })
966          .collect::<Vec<_>>();
967
968        let incoming_connections =
969          module_graph.get_incoming_connections_by_origin_module(&module_id);
970        let (incoming_connections_from_non_modules, incoming_connections_from_modules) =
971          incoming_connections.into_parts();
972        let incomings = IncomingConnections {
973          from_non_modules: incoming_connections_from_non_modules
974            .into_iter()
975            .cloned()
976            .collect(),
977          from_modules: incoming_connections_from_modules
978            .into_iter()
979            .map(|(origin_module, connections)| {
980              (origin_module, connections.into_iter().cloned().collect())
981            })
982            .collect(),
983        };
984        let incoming_connections_len = incomings.from_non_modules.len()
985          + incomings
986            .from_modules
987            .values()
988            .map(std::vec::Vec::len)
989            .sum::<usize>();
990        let mut active_incomings =
991          HashMap::with_capacity_and_hasher(incoming_connections_len, Default::default());
992        for connection in incomings
993          .from_non_modules
994          .iter()
995          .chain(incomings.from_modules.values().flatten())
996        {
997          active_incomings.insert(
998            connection.dependency_id,
999            connection.is_active(
1000              module_graph,
1001              Some(&runtime),
1002              module_graph_cache,
1003              &compilation
1004                .build_module_graph_artifact
1005                .side_effects_state_artifact,
1006              &compilation.exports_info_artifact,
1007            ),
1008          );
1009        }
1010        let number_of_chunks = compilation
1011          .build_chunk_graph_artifact
1012          .chunk_graph
1013          .get_number_of_module_chunks(module_id);
1014        (
1015          module_id,
1016          NoRuntimeModuleCache {
1017            runtime,
1018            provided_names,
1019            connections,
1020            incomings,
1021            active_incomings,
1022            number_of_chunks,
1023          },
1024        )
1025      })
1026      .collect::<Vec<_>>();
1027    let mut modules_without_runtime_cache = IdentifierMap::with_capacity_and_hasher(
1028      modules_without_runtime_cache_entries.len(),
1029      Default::default(),
1030    );
1031    modules_without_runtime_cache.extend(modules_without_runtime_cache_entries);
1032
1033    for current_root in relevant_modules.iter() {
1034      if used_as_inner.contains(current_root) {
1035        continue;
1036      }
1037
1038      let NoRuntimeModuleCache { runtime, .. } = modules_without_runtime_cache
1039        .get(current_root)
1040        .expect("should have module");
1041      let module_graph = compilation.get_module_graph();
1042      let module_graph_cache = &compilation.module_graph_cache_artifact;
1043      let exports_info = compilation
1044        .exports_info_artifact
1045        .get_exports_info_data(current_root);
1046      let filtered_runtime = filter_runtime(Some(runtime), |r| exports_info.is_module_used(r));
1047      let active_runtime = match filtered_runtime {
1048        RuntimeCondition::Boolean(true) => Some(runtime.clone()),
1049        RuntimeCondition::Boolean(false) => None,
1050        RuntimeCondition::Spec(spec) => Some(spec),
1051      };
1052
1053      let mut current_configuration =
1054        ConcatConfiguration::new(*current_root, active_runtime.clone());
1055
1056      let mut failure_cache = IdentifierMap::default();
1057      let mut success_cache = RuntimeIdentifierCache::default();
1058      let mut candidates_visited = IdentifierSet::default();
1059      let mut candidates = VecDeque::new();
1060      let imports = {
1061        let side_effects_state_artifact = compilation
1062          .build_module_graph_artifact
1063          .side_effects_state_artifact
1064          .clone();
1065        let module_graph_artifacts = ModuleGraphArtifacts {
1066          mg_cache: module_graph_cache,
1067          side_effects_state_artifact: &side_effects_state_artifact,
1068          exports_info_artifact: &compilation.exports_info_artifact,
1069        };
1070
1071        Self::get_imports(
1072          module_graph,
1073          &module_graph_artifacts,
1074          *current_root,
1075          active_runtime.as_ref(),
1076          &mut imports_cache,
1077          &modules_without_runtime_cache,
1078        )
1079      };
1080      for import in imports {
1081        candidates.push_back(import);
1082      }
1083
1084      let mut import_candidates = IdentifierSet::default();
1085      while let Some(imp) = candidates.pop_front() {
1086        if candidates_visited.contains(&imp) {
1087          continue;
1088        }
1089        candidates_visited.insert(imp);
1090        import_candidates.clear();
1091        match Self::try_to_add(
1092          compilation,
1093          &mut current_configuration,
1094          &imp,
1095          Some(runtime),
1096          active_runtime.as_ref(),
1097          &possible_inners,
1098          &mut import_candidates,
1099          &mut failure_cache,
1100          &mut success_cache,
1101          true,
1102          &mut statistics,
1103          &mut imports_cache,
1104          &modules_without_runtime_cache,
1105        ) {
1106          Some(problem) => {
1107            failure_cache.insert(imp, problem.clone());
1108            current_configuration.add_warning(imp, problem);
1109          }
1110          _ => {
1111            import_candidates.iter().for_each(|c: &ModuleIdentifier| {
1112              candidates.push_back(*c);
1113            });
1114          }
1115        }
1116      }
1117      stats_candidates += candidates.len();
1118      if !current_configuration.is_empty() {
1119        let modules = current_configuration.get_modules();
1120        stats_size_sum += modules.len();
1121        let root_module = current_configuration.root_module;
1122
1123        modules.iter().for_each(|module| {
1124          if *module != root_module {
1125            used_as_inner.insert(*module);
1126          }
1127        });
1128        concat_configurations.push(current_configuration);
1129      } else {
1130        stats_empty_configurations += 1;
1131        let module_graph = compilation.get_module_graph_mut();
1132        let optimization_bailouts = module_graph.get_optimization_bailout_mut(current_root);
1133        for warning in current_configuration.get_warnings_sorted() {
1134          optimization_bailouts.push(OptimizationBailoutItem::Message(
1135            self.format_bailout_warning(warning.0, &warning.1),
1136          ));
1137        }
1138      }
1139    }
1140
1141    logger.time_end(start);
1142
1143    rayon::spawn(move || drop(modules_without_runtime_cache));
1144
1145    if !concat_configurations.is_empty() {
1146      let mut concat_len_buffer = itoa::Buffer::new();
1147      let concat_len_str = concat_len_buffer.format(concat_configurations.len());
1148      let mut avg_size_buffer = itoa::Buffer::new();
1149      let avg_size_str = avg_size_buffer.format(stats_size_sum / concat_configurations.len());
1150      let mut empty_configs_buffer = itoa::Buffer::new();
1151      let empty_configs_str = empty_configs_buffer.format(stats_empty_configurations);
1152      logger.debug(format!(
1153        "{concat_len_str} successful concat configurations (avg size: {avg_size_str}), {empty_configs_str} bailed out completely"
1154      ));
1155    }
1156
1157    let mut candidates_buffer = itoa::Buffer::new();
1158    let candidates_str = candidates_buffer.format(stats_candidates);
1159    let mut cached_buffer = itoa::Buffer::new();
1160    let cached_str = cached_buffer.format(statistics.cached);
1161    let mut already_in_config_buffer = itoa::Buffer::new();
1162    let already_in_config_str = already_in_config_buffer.format(statistics.already_in_config);
1163    let mut invalid_module_buffer = itoa::Buffer::new();
1164    let invalid_module_str = invalid_module_buffer.format(statistics.invalid_module);
1165    let mut incorrect_chunks_buffer = itoa::Buffer::new();
1166    let incorrect_chunks_str = incorrect_chunks_buffer.format(statistics.incorrect_chunks);
1167    let mut incorrect_dependency_buffer = itoa::Buffer::new();
1168    let incorrect_dependency_str =
1169      incorrect_dependency_buffer.format(statistics.incorrect_dependency);
1170    let mut incorrect_chunks_of_importer_buffer = itoa::Buffer::new();
1171    let incorrect_chunks_of_importer_str =
1172      incorrect_chunks_of_importer_buffer.format(statistics.incorrect_chunks_of_importer);
1173    let mut incorrect_module_dependency_buffer = itoa::Buffer::new();
1174    let incorrect_module_dependency_str =
1175      incorrect_module_dependency_buffer.format(statistics.incorrect_module_dependency);
1176    let mut incorrect_runtime_condition_buffer = itoa::Buffer::new();
1177    let incorrect_runtime_condition_str =
1178      incorrect_runtime_condition_buffer.format(statistics.incorrect_runtime_condition);
1179    let mut importer_failed_buffer = itoa::Buffer::new();
1180    let importer_failed_str = importer_failed_buffer.format(statistics.importer_failed);
1181    let mut added_buffer = itoa::Buffer::new();
1182    let added_str = added_buffer.format(statistics.added);
1183    logger.debug(format!(
1184        "{candidates_str} candidates were considered for adding ({cached_str} cached failure, {already_in_config_str} already in config, {invalid_module_str} invalid module, {incorrect_chunks_str} incorrect chunks, {incorrect_dependency_str} incorrect dependency, {incorrect_chunks_of_importer_str} incorrect chunks of importer, {incorrect_module_dependency_str} incorrect module dependency, {incorrect_runtime_condition_str} incorrect runtime condition, {importer_failed_str} importer failed, {added_str} added)"
1185    ));
1186
1187    // Copy from  https://github.com/webpack/webpack/blob/1f99ad6367f2b8a6ef17cce0e058f7a67fb7db18/lib/optimize/ModuleConcatenationPlugin.js#L368-L371
1188    // HACK: Sort configurations by length and start with the longest one
1189    // to get the biggest groups possible. Used modules are marked with usedModules
1190    // TODO(from webpack): Allow reusing existing configuration while trying to add dependencies.
1191    // This would improve performance. O(n^2) -> O(n)
1192    let start = logger.time("sort concat configurations");
1193    concat_configurations.sort_by_key(|b| std::cmp::Reverse(b.modules.len()));
1194    logger.time_end(start);
1195
1196    let mut used_modules = IdentifierSet::default();
1197    let mut batch = vec![];
1198
1199    for config in concat_configurations {
1200      if used_modules.contains(&config.root_module) {
1201        continue;
1202      }
1203      let modules_set = config.get_modules();
1204      used_modules.extend(modules_set.iter().copied());
1205      batch.push(config);
1206    }
1207
1208    let new_modules = rspack_parallel::scope::<_, Result<_>>(|token| {
1209      batch.into_iter().for_each(|config| {
1210        let s = unsafe { token.used(&*compilation) };
1211        s.spawn(move |compilation| async move {
1212          let modules_set = config.get_modules();
1213          let new_module = create_concatenated_module(compilation, &config).await?;
1214          let new_module_id = new_module.identifier();
1215          let connections = prepare_concatenated_module_connections(
1216            compilation,
1217            &new_module_id,
1218            modules_set,
1219            |m, con, dep| {
1220              con.original_module_identifier.as_ref() == Some(m)
1221                && !(is_esm_dep_like(dep) && modules_set.contains(con.module_identifier()))
1222            },
1223          );
1224          let (root_outgoings, root_incomings) = prepare_concatenated_root_module_connections(
1225            compilation,
1226            &config.root_module,
1227            |m, c, dep| {
1228              let other_module = if c.module_identifier() == m {
1229                c.original_module_identifier
1230              } else {
1231                Some(*c.module_identifier())
1232              };
1233              let inner_connection = is_esm_dep_like(dep)
1234                && if let Some(other_module) = other_module {
1235                  modules_set.contains(&other_module)
1236                } else {
1237                  false
1238                };
1239              !inner_connection
1240            },
1241          );
1242          Ok((
1243            new_module,
1244            connections,
1245            root_outgoings,
1246            root_incomings,
1247            config,
1248          ))
1249        });
1250      });
1251    })
1252    .await
1253    .into_iter()
1254    .map(|r| r.to_rspack_result())
1255    .collect::<Result<Vec<_>>>()?;
1256
1257    let mut set_original_mid_tasks = vec![];
1258    let mut set_mid_tasks = vec![];
1259    let mut add_connection_tasks = vec![];
1260    let mut remove_connection_tasks = vec![];
1261
1262    for res in new_modules {
1263      let (new_module, outgoings, root_outgoings, root_incomings, config) = res?;
1264      let new_module_id = new_module.identifier();
1265      let root_module_id = config.root_module;
1266      add_concatenated_module(compilation, new_module, config);
1267
1268      for connection in outgoings.iter().chain(root_outgoings.iter()) {
1269        set_original_mid_tasks.push((*connection, new_module_id));
1270      }
1271      for connection in root_incomings.iter() {
1272        set_mid_tasks.push((*connection, new_module_id));
1273      }
1274      let mut all_outgoings = outgoings;
1275      all_outgoings.extend(root_outgoings.clone());
1276      add_connection_tasks.push((new_module_id, all_outgoings, root_incomings.clone()));
1277      remove_connection_tasks.push((root_module_id, root_outgoings, root_incomings));
1278    }
1279
1280    let module_graph = compilation.get_module_graph_mut();
1281    module_graph.batch_set_connections_original_module(set_original_mid_tasks);
1282    module_graph.batch_set_connections_module(set_mid_tasks);
1283    module_graph.batch_add_connections(add_connection_tasks);
1284    module_graph.batch_remove_connections(remove_connection_tasks);
1285
1286    Ok(())
1287  }
1288}
1289
1290#[plugin_hook(CompilationOptimizeChunkModules for ModuleConcatenationPlugin)]
1291async fn optimize_chunk_modules(&self, compilation: &mut Compilation) -> Result<Option<bool>> {
1292  if let Some(diagnostic) = compilation.incremental.disable_passes(
1293    IncrementalPasses::MODULES_HASHES
1294    | IncrementalPasses::MODULE_IDS
1295    | IncrementalPasses::CHUNK_IDS
1296    | IncrementalPasses::CHUNKS_RUNTIME_REQUIREMENTS
1297    | IncrementalPasses::CHUNKS_HASHES,
1298    "ModuleConcatenationPlugin (optimization.concatenateModules = true)",
1299    "it requires calculating the modules that can be concatenated based on all the modules, which is a global effect",
1300  ) && let Some(diagnostic) = diagnostic {
1301      compilation.push_diagnostic(diagnostic);
1302  }
1303
1304  self.optimize_chunk_modules_impl(compilation).await?;
1305
1306  Ok(None)
1307}
1308
1309impl Plugin for ModuleConcatenationPlugin {
1310  fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
1311    ctx
1312      .compilation_hooks
1313      .optimize_chunk_modules
1314      .tap(optimize_chunk_modules::new(self));
1315    Ok(())
1316  }
1317}
1318
1319#[derive(Debug, Default)]
1320struct Statistics {
1321  cached: u32,
1322  already_in_config: u32,
1323  invalid_module: u32,
1324  incorrect_chunks: u32,
1325  incorrect_dependency: u32,
1326  incorrect_module_dependency: u32,
1327  incorrect_chunks_of_importer: u32,
1328  incorrect_runtime_condition: u32,
1329  importer_failed: u32,
1330  cache_hit: u32,
1331  module_visit: IdentifierMap<usize>,
1332  added: u32,
1333}
1334
1335#[derive(Debug, Default)]
1336struct IncomingConnections {
1337  from_non_modules: Vec<ModuleGraphConnection>,
1338  from_modules: IdentifierMap<Vec<ModuleGraphConnection>>,
1339}
1340
1341#[derive(Debug)]
1342pub struct NoRuntimeModuleCache {
1343  runtime: RuntimeSpec,
1344  provided_names: bool,
1345  connections: Vec<(ModuleGraphConnection, (bool, bool))>,
1346  incomings: IncomingConnections,
1347  active_incomings: HashMap<DependencyId, bool>,
1348  number_of_chunks: usize,
1349}
1350
1351async fn create_concatenated_module(
1352  compilation: &Compilation,
1353  config: &ConcatConfiguration,
1354) -> Result<BoxModule> {
1355  let module_graph = compilation.get_module_graph();
1356  let root_module_id = config.root_module;
1357  let modules_set = config.get_modules();
1358
1359  let root_module = module_graph
1360    .module_by_identifier(&root_module_id)
1361    .expect("should have module");
1362
1363  let root_module_ctxt = RootModuleContext {
1364    id: root_module_id,
1365    readable_identifier: get_cached_readable_identifier(
1366      &root_module_id,
1367      module_graph,
1368      &compilation.module_static_cache,
1369      &compilation.options.context,
1370    ),
1371    name_for_condition: root_module.name_for_condition(),
1372    lib_indent: root_module
1373      .lib_ident(LibIdentOptions {
1374        context: compilation.options.context.as_str(),
1375      })
1376      .map(|id| id.to_string()),
1377    layer: root_module.get_layer().cloned(),
1378    resolve_options: root_module.get_resolve_options(),
1379    code_generation_dependencies: root_module
1380      .get_code_generation_dependencies()
1381      .map(|deps| deps.to_vec()),
1382    presentational_dependencies: root_module
1383      .get_presentational_dependencies()
1384      .map(|deps| deps.to_vec()),
1385    context: Some(compilation.options.context.clone()),
1386    side_effect_connection_state: root_module.get_side_effects_connection_state(
1387      module_graph,
1388      &compilation.module_graph_cache_artifact,
1389      &compilation
1390        .build_module_graph_artifact
1391        .side_effects_state_artifact,
1392      &mut IdentifierSet::default(),
1393      &mut IdentifierMap::default(),
1394    ),
1395    factory_meta: root_module.factory_meta().cloned(),
1396    build_meta: root_module.build_meta().clone(),
1397    module_argument: root_module.get_module_argument(),
1398    exports_argument: root_module.get_exports_argument(),
1399  };
1400  let modules = modules_set
1401    .iter()
1402    .map(|id| {
1403      let module = module_graph
1404        .module_by_identifier(id)
1405        .unwrap_or_else(|| panic!("should have module {id}"));
1406
1407      ConcatenatedInnerModule {
1408        id: *id,
1409        size: module.size(
1410          Some(&rspack_core::SourceType::JavaScript),
1411          Some(compilation),
1412        ),
1413        shorten_id: get_cached_readable_identifier(
1414          id,
1415          module_graph,
1416          &compilation.module_static_cache,
1417          &compilation.options.context,
1418        ),
1419      }
1420    })
1421    .collect::<Vec<_>>();
1422  let mut new_module = BoxModule::new(Box::from(ConcatenatedModule::create(
1423    root_module_ctxt,
1424    modules,
1425    Some(rspack_hash::HashFunction::Xxhash64),
1426    config.runtime.clone(),
1427    compilation,
1428  )));
1429  let build_result = new_module
1430    .build(
1431      rspack_core::BuildContext {
1432        compiler_id: compilation.compiler_id(),
1433        compilation_id: compilation.id(),
1434        resolver_factory: compilation.resolver_factory.clone(),
1435        plugin_driver: compilation.plugin_driver.clone(),
1436        compiler_options: compilation.options.clone(),
1437        fs: compilation.input_filesystem.clone(),
1438        runtime_template: compilation.runtime_template.create_module_code_template(),
1439      },
1440      Some(compilation),
1441    )
1442    .await?;
1443  new_module = build_result.module;
1444
1445  Ok(new_module)
1446}
1447
1448fn prepare_concatenated_module_connections<F>(
1449  compilation: &Compilation,
1450  new_module: &ModuleIdentifier,
1451  modules_set: &IdentifierIndexSet,
1452  filter_connection: F,
1453) -> Vec<DependencyId>
1454where
1455  F: Fn(&ModuleIdentifier, &ModuleGraphConnection, &BoxDependency) -> bool + Sync,
1456{
1457  let mg = compilation.get_module_graph();
1458
1459  let dependency_parts = modules_set
1460    .par_iter()
1461    .filter_map(|m| {
1462      if m == new_module {
1463        return None;
1464      }
1465      let old_mgm_connections = mg
1466        .module_graph_module_by_identifier(m)
1467        .expect("should have mgm")
1468        .outgoing_connections();
1469
1470      let mut part = vec![];
1471      for dep_id in old_mgm_connections {
1472        let connection = mg
1473          .connection_by_dependency_id(dep_id)
1474          .expect("should have connection");
1475        let dep = mg.dependency_by_id(dep_id);
1476        if filter_connection(m, connection, dep) {
1477          part.push(*dep_id);
1478        }
1479      }
1480      Some(part)
1481    })
1482    .collect::<Vec<_>>();
1483
1484  let mut res = vec![];
1485  for part in dependency_parts {
1486    res.extend(part);
1487  }
1488  res
1489}
1490
1491fn prepare_concatenated_root_module_connections<F>(
1492  compilation: &Compilation,
1493  root_module_id: &ModuleIdentifier,
1494  filter_connection: F,
1495) -> (Vec<DependencyId>, Vec<DependencyId>)
1496where
1497  F: Fn(&ModuleIdentifier, &ModuleGraphConnection, &BoxDependency) -> bool,
1498{
1499  let mg = compilation.get_module_graph();
1500  let mut outgoings = vec![];
1501  let old_mgm_connections = mg
1502    .module_graph_module_by_identifier(root_module_id)
1503    .expect("should have mgm")
1504    .outgoing_connections();
1505
1506  for dep_id in old_mgm_connections {
1507    let connection = mg
1508      .connection_by_dependency_id(dep_id)
1509      .expect("should have connection");
1510
1511    let dep = mg.dependency_by_id(dep_id);
1512    if filter_connection(root_module_id, connection, dep) {
1513      outgoings.push(*dep_id);
1514    }
1515  }
1516
1517  let mut incomings = vec![];
1518  let incoming_connections = mg
1519    .module_graph_module_by_identifier(root_module_id)
1520    .expect("should have mgm")
1521    .incoming_connections();
1522
1523  for dep_id in incoming_connections {
1524    let connection = mg
1525      .connection_by_dependency_id(dep_id)
1526      .expect("should have connection");
1527    let dependency = mg.dependency_by_id(dep_id);
1528    if filter_connection(root_module_id, connection, dependency) {
1529      incomings.push(*dep_id);
1530    }
1531  }
1532
1533  (outgoings, incomings)
1534}
1535
1536fn add_concatenated_module(
1537  compilation: &mut Compilation,
1538  new_module: BoxModule,
1539  config: ConcatConfiguration,
1540) {
1541  let root_module_id = config.root_module;
1542  let modules_set = config.get_modules();
1543
1544  let module_graph = compilation.get_module_graph();
1545  let box_module = module_graph
1546    .module_by_identifier(&root_module_id)
1547    .expect("should have module");
1548  let root_module_source_types = box_module.source_types(module_graph);
1549  let is_root_module_asset_module = root_module_source_types.contains(&SourceType::Asset);
1550
1551  let mut chunk_graph = std::mem::take(&mut compilation.build_chunk_graph_artifact.chunk_graph);
1552  let module_graph = compilation.get_module_graph_mut();
1553
1554  let module_graph_module = ModuleGraphModule::new(new_module.identifier());
1555  module_graph.add_module_graph_module(module_graph_module);
1556  ModuleGraph::clone_module_attributes(compilation, &root_module_id, &new_module.identifier());
1557  // integrate
1558
1559  let module_graph = compilation.get_module_graph_mut();
1560
1561  for m in modules_set.iter() {
1562    if *m == root_module_id {
1563      continue;
1564    }
1565    let module = module_graph
1566      .module_by_identifier(m)
1567      .expect("should exist module");
1568    // TODO: optimize asset module https://github.com/webpack/webpack/pull/15515/files
1569    for chunk_ukey in chunk_graph.get_module_chunks(root_module_id).clone() {
1570      let source_types =
1571        chunk_graph.get_chunk_module_source_types(&chunk_ukey, module, module_graph);
1572
1573      if source_types.len() == 1 {
1574        chunk_graph.disconnect_chunk_and_module(&chunk_ukey, *m);
1575      } else {
1576        let new_source_types = source_types
1577          .into_iter()
1578          .filter(|source_type| !matches!(source_type, SourceType::JavaScript))
1579          .collect();
1580        chunk_graph.set_chunk_modules_source_types(&chunk_ukey, *m, new_source_types)
1581      }
1582    }
1583  }
1584
1585  // different from webpack
1586  // Rspack: if entry is an asset module, outputs a js chunk and a asset chunk
1587  // Webpack: if entry is an asset module, outputs an asset chunk
1588  // these lines of codes fix a bug: when asset module (NormalModule) is concatenated into ConcatenatedModule, the asset will be lost
1589  // because `chunk_graph.replace_module(&root_module_id, &new_module.id());` will remove the asset module from chunk, and I add this module back to fix this bug
1590  if is_root_module_asset_module {
1591    chunk_graph.replace_module(&root_module_id, &new_module.identifier());
1592    chunk_graph.add_module(root_module_id);
1593    for chunk_ukey in chunk_graph
1594      .get_module_chunks(new_module.identifier())
1595      .clone()
1596    {
1597      let module = module_graph
1598        .module_by_identifier(&root_module_id)
1599        .expect("should exist module");
1600
1601      let source_types =
1602        chunk_graph.get_chunk_module_source_types(&chunk_ukey, module, module_graph);
1603      let new_source_types = source_types
1604        .iter()
1605        .filter(|source_type| !matches!(source_type, SourceType::JavaScript))
1606        .copied()
1607        .collect();
1608      chunk_graph.set_chunk_modules_source_types(&chunk_ukey, root_module_id, new_source_types);
1609      chunk_graph.connect_chunk_and_module(chunk_ukey, root_module_id);
1610    }
1611  } else {
1612    chunk_graph.replace_module(&root_module_id, &new_module.identifier());
1613  }
1614
1615  module_graph.add_module(new_module);
1616  compilation.build_chunk_graph_artifact.chunk_graph = chunk_graph;
1617}
1618
1619fn is_connection_active_in_runtime(
1620  connection: &ModuleGraphConnection,
1621  runtime: Option<&RuntimeSpec>,
1622  cached_active_incomings: &HashMap<DependencyId, bool>,
1623  cached_runtime: &RuntimeSpec,
1624  mg: &ModuleGraph,
1625  artifacts: &ModuleGraphArtifacts,
1626) -> bool {
1627  if let (Some(cached_active), Some(runtime)) = (
1628    cached_active_incomings.get(&connection.dependency_id),
1629    runtime,
1630  ) {
1631    if runtime == cached_runtime {
1632      return *cached_active;
1633    }
1634
1635    if *cached_active && cached_runtime.is_subset(runtime) {
1636      return true;
1637    }
1638
1639    if !*cached_active && cached_runtime.is_superset(runtime) {
1640      return false;
1641    }
1642  }
1643
1644  connection.is_active(
1645    mg,
1646    runtime,
1647    artifacts.mg_cache,
1648    artifacts.side_effects_state_artifact,
1649    artifacts.exports_info_artifact,
1650  )
1651}