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