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 *cached_active
212 } else if *cached_active && cached.runtime.is_subset(runtime) {
213 true
215 } else if !*cached_active && cached.runtime.is_superset(runtime) {
216 false
218 } else {
219 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 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 if has_active_non_modules_connections {
381 let problem = {
382 format!(
389 "Module {module_readable_identifier} is referenced",
390 )
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 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 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 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 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 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 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 })
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 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 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 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 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}