1use std::rc::Rc;
2
3use rayon::prelude::*;
4use rspack_collections::{IdentifierMap, IdentifierSet};
5use rspack_core::{
6 AsyncModulesArtifact, BuildMetaExportsType, Compilation, CompilationFinishModules,
7 DependenciesBlock, DependencyId, EvaluatedInlinableValue, ExportInfo, ExportInfoData,
8 ExportNameOrSpec, ExportProvided, ExportSpecExports, ExportsInfo, ExportsInfoData,
9 ExportsOfExportsSpec, ExportsSpec, GetTargetResult, Logger, ModuleGraph,
10 ModuleGraphCacheArtifact, ModuleGraphConnection, ModuleIdentifier, Nullable, Plugin,
11 PrefetchExportsInfoMode, get_target,
12 incremental::{self, IncrementalPasses},
13};
14use rspack_error::Result;
15use rspack_hook::{plugin, plugin_hook};
16use rspack_util::fx_hash::{FxIndexMap, FxIndexSet};
17use rustc_hash::FxHashSet;
18use swc_core::ecma::atoms::Atom;
19
20struct FlagDependencyExportsState<'a> {
21 mg: &'a mut ModuleGraph,
22 mg_cache: &'a ModuleGraphCacheArtifact,
23}
24
25impl<'a> FlagDependencyExportsState<'a> {
26 pub fn new(mg: &'a mut ModuleGraph, mg_cache: &'a ModuleGraphCacheArtifact) -> Self {
27 Self { mg, mg_cache }
28 }
29
30 pub fn apply(&mut self, modules: IdentifierSet) {
31 for module_id in &modules {
33 let exports_type_unset = self
34 .mg
35 .module_by_identifier(module_id)
36 .expect("should have module")
37 .build_meta()
38 .exports_type
39 == BuildMetaExportsType::Unset;
40 let exports_info = self.mg.get_exports_info_data_mut(module_id);
42 exports_info.reset_provide_info();
44 if exports_type_unset
45 && !matches!(
46 exports_info.other_exports_info().provided(),
47 Some(ExportProvided::Unknown)
48 )
49 {
50 exports_info.set_has_provide_info();
51 exports_info.set_unknown_exports_provided(false, None, None, None, None);
52 continue;
53 }
54
55 exports_info.set_has_provide_info();
56 }
57
58 let mut batch = modules;
62 let mut dependencies: IdentifierMap<IdentifierSet> = IdentifierMap::default();
63 while !batch.is_empty() {
64 let modules = std::mem::take(&mut batch);
65
66 let module_exports_specs = modules
68 .into_par_iter()
69 .map(|module_id| {
70 let exports_specs =
71 collect_module_exports_specs(&module_id, self.mg, self.mg_cache).unwrap_or_default();
72 (module_id, exports_specs)
73 })
74 .collect::<Vec<_>>();
75
76 let mut changed_modules = FxHashSet::default();
77
78 let (non_nested_specs, has_nested_specs): (Vec<_>, Vec<_>) = module_exports_specs
89 .into_iter()
90 .partition(|(_mid, (_, has_nested_exports))| {
91 if *has_nested_exports {
92 return false;
93 }
94 true
95 });
96
97 let non_nested_tasks = non_nested_specs
99 .into_iter()
100 .map(|(module_id, (exports_specs, _))| {
101 let exports_info = self.mg.get_exports_info_data(&module_id).clone();
102 (module_id, exports_info, exports_specs)
103 })
104 .par_bridge()
105 .map(|(module_id, mut exports_info, exports_specs)| {
106 let mut changed = false;
107 let mut dependencies = vec![];
108 for (dep_id, exports_spec) in exports_specs.into_iter() {
109 let (is_changed, changed_dependencies) = process_exports_spec_without_nested(
110 self.mg,
111 &module_id,
112 dep_id,
113 &exports_spec,
114 &mut exports_info,
115 );
116 changed |= is_changed;
117 dependencies.extend(changed_dependencies);
118 }
119 (module_id, changed, dependencies, exports_info)
120 })
121 .collect::<Vec<_>>();
122
123 for (module_id, changed, changed_dependencies, exports_info) in non_nested_tasks {
125 if changed {
126 changed_modules.insert(module_id);
127 }
128 for (module_id, dep_id) in changed_dependencies {
129 dependencies.entry(module_id).or_default().insert(dep_id);
130 }
131 self.mg.set_exports_info(exports_info.id(), exports_info);
132 }
133
134 for (module_id, (exports_specs, _)) in has_nested_specs {
136 let mut changed = false;
137 for (dep_id, exports_spec) in exports_specs.into_iter() {
138 let (is_changed, changed_dependencies) =
139 process_exports_spec(self.mg, &module_id, dep_id, &exports_spec);
140 changed |= is_changed;
141 for (module_id, dep_id) in changed_dependencies {
142 dependencies.entry(module_id).or_default().insert(dep_id);
143 }
144 }
145 if changed {
146 changed_modules.insert(module_id);
147 }
148 }
149
150 batch.extend(changed_modules.into_iter().flat_map(|m| {
152 dependencies
153 .get(&m)
154 .into_iter()
155 .flat_map(|d| d.iter())
156 .copied()
157 }));
158 }
159 }
160}
161
162#[derive(Debug, Clone)]
164pub struct DefaultExportInfo<'a> {
165 can_mangle: Option<bool>,
166 terminal_binding: bool,
167 from: Option<&'a ModuleGraphConnection>,
168 priority: Option<u8>,
169}
170
171#[plugin]
172#[derive(Debug, Default)]
173pub struct FlagDependencyExportsPlugin;
174
175#[plugin_hook(CompilationFinishModules for FlagDependencyExportsPlugin)]
176async fn finish_modules(
177 &self,
178 compilation: &mut Compilation,
179 _async_modules_artifact: &mut AsyncModulesArtifact,
180) -> Result<()> {
181 let modules: IdentifierSet = if let Some(mutations) = compilation
182 .incremental
183 .mutations_read(IncrementalPasses::PROVIDED_EXPORTS)
184 {
185 let modules = mutations.get_affected_modules_with_module_graph(compilation.get_module_graph());
186 tracing::debug!(target: incremental::TRACING_TARGET, passes = %IncrementalPasses::PROVIDED_EXPORTS, %mutations, ?modules);
187 let logger = compilation.get_logger("rspack.incremental.providedExports");
188 logger.log(format!(
189 "{} modules are affected, {} in total",
190 modules.len(),
191 compilation.get_module_graph().modules().len()
192 ));
193 modules
194 } else {
195 compilation
196 .get_module_graph()
197 .modules()
198 .keys()
199 .copied()
200 .collect()
201 };
202 let module_graph_cache = compilation.module_graph_cache_artifact.clone();
203
204 let module_graph = compilation
205 .build_module_graph_artifact
206 .get_module_graph_mut();
207 FlagDependencyExportsState::new(module_graph, &module_graph_cache).apply(modules);
208 Ok(())
209}
210
211impl Plugin for FlagDependencyExportsPlugin {
212 fn name(&self) -> &'static str {
213 "FlagDependencyExportsPlugin"
214 }
215
216 fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
217 ctx
218 .compilation_hooks
219 .finish_modules
220 .tap(finish_modules::new(self));
221 Ok(())
222 }
223}
224
225fn collect_module_exports_specs(
230 module_id: &ModuleIdentifier,
231 mg: &ModuleGraph,
232 mg_cache: &ModuleGraphCacheArtifact,
233) -> Option<(FxIndexMap<DependencyId, ExportsSpec>, bool)> {
234 let mut has_nested_exports = false;
235 fn walk_block<B: DependenciesBlock + ?Sized>(
236 block: &B,
237 dep_ids: &mut FxIndexSet<DependencyId>,
238 mg: &ModuleGraph,
239 ) {
240 dep_ids.extend(block.get_dependencies().iter().copied());
241 for block_id in block.get_blocks() {
242 if let Some(block) = mg.block_by_id(block_id) {
243 walk_block(block, dep_ids, mg);
244 }
245 }
246 }
247
248 let block = mg.module_by_identifier(module_id)?.as_ref();
249 let mut dep_ids = FxIndexSet::default();
250 walk_block(block, &mut dep_ids, mg);
251
252 let res = dep_ids
256 .into_iter()
257 .filter_map(|id| {
258 let dep = mg.dependency_by_id(&id);
259 let exports_spec = dep.get_exports(mg, mg_cache)?;
260 has_nested_exports |= exports_spec.has_nested_exports();
261 Some((id, exports_spec))
262 })
263 .collect::<FxIndexMap<DependencyId, ExportsSpec>>();
264 Some((res, has_nested_exports))
266}
267
268pub fn process_exports_spec(
272 mg: &mut ModuleGraph,
273 module_id: &ModuleIdentifier,
274 dep_id: DependencyId,
275 export_desc: &ExportsSpec,
276) -> (bool, Vec<(ModuleIdentifier, ModuleIdentifier)>) {
277 let mut changed = false;
278 let mut dependencies = vec![];
279 let exports = &export_desc.exports;
280 let global_can_mangle = &export_desc.can_mangle;
281 let global_from = export_desc.from.as_ref();
282 let global_priority = &export_desc.priority;
283 let global_terminal_binding = export_desc.terminal_binding.unwrap_or(false);
284 let export_dependencies = &export_desc.dependencies;
285 if let Some(hide_export) = &export_desc.hide_export {
286 let exports_info = mg.get_exports_info_data_mut(module_id);
287 for name in hide_export.iter() {
288 exports_info.ensure_export_info(name);
289 }
290 for name in hide_export.iter() {
291 exports_info
292 .named_exports_mut(name)
293 .expect("should have named export")
294 .unset_target(&dep_id);
295 }
296 }
297 match exports {
298 ExportsOfExportsSpec::UnknownExports => {
299 changed |= mg
300 .get_exports_info_data_mut(module_id)
301 .set_unknown_exports_provided(
302 global_can_mangle.unwrap_or_default(),
303 export_desc.exclude_exports.as_ref(),
304 global_from.map(|_| dep_id),
305 global_from.map(|_| dep_id),
306 *global_priority,
307 );
308 }
309 ExportsOfExportsSpec::NoExports => {}
310 ExportsOfExportsSpec::Names(ele) => {
311 let (merge_changed, merge_dependencies) = merge_exports(
312 mg,
313 module_id,
314 mg.get_exports_info(module_id),
315 ele,
316 DefaultExportInfo {
317 can_mangle: *global_can_mangle,
318 terminal_binding: global_terminal_binding,
319 from: global_from,
320 priority: *global_priority,
321 },
322 dep_id,
323 );
324 changed |= merge_changed;
325 dependencies.extend(merge_dependencies);
326 }
327 }
328
329 if let Some(export_dependencies) = export_dependencies {
330 for export_dep in export_dependencies {
331 dependencies.push((*export_dep, *module_id));
332 }
333 }
334
335 (changed, dependencies)
336}
337
338pub fn process_exports_spec_without_nested(
344 mg: &ModuleGraph,
345 module_id: &ModuleIdentifier,
346 dep_id: DependencyId,
347 export_desc: &ExportsSpec,
348 exports_info: &mut ExportsInfoData,
349) -> (bool, Vec<(ModuleIdentifier, ModuleIdentifier)>) {
350 let mut changed = false;
351 let mut dependencies = vec![];
352
353 let exports = &export_desc.exports;
354 let global_can_mangle = &export_desc.can_mangle;
355 let global_from = export_desc.from.as_ref();
356 let global_priority = &export_desc.priority;
357 let global_terminal_binding = export_desc.terminal_binding.unwrap_or(false);
358 let export_dependencies = &export_desc.dependencies;
359 if let Some(hide_export) = &export_desc.hide_export {
360 for name in hide_export.iter() {
361 exports_info
362 .ensure_owned_export_info(name)
363 .unset_target(&dep_id);
364 }
365 }
366 match exports {
367 ExportsOfExportsSpec::UnknownExports => {
368 changed |= exports_info.set_unknown_exports_provided(
369 global_can_mangle.unwrap_or_default(),
370 export_desc.exclude_exports.as_ref(),
371 global_from.map(|_| dep_id),
372 global_from.map(|_| dep_id),
373 *global_priority,
374 );
375 }
376 ExportsOfExportsSpec::NoExports => {}
377 ExportsOfExportsSpec::Names(ele) => {
378 let (merge_changed, merge_dependencies) = merge_exports_without_nested(
379 mg,
380 module_id,
381 exports_info,
382 ele,
383 DefaultExportInfo {
384 can_mangle: *global_can_mangle,
385 terminal_binding: global_terminal_binding,
386 from: global_from,
387 priority: *global_priority,
388 },
389 dep_id,
390 );
391 changed |= merge_changed;
392 dependencies.extend(merge_dependencies);
393 }
394 }
395
396 if let Some(export_dependencies) = export_dependencies {
397 for export_dep in export_dependencies {
398 dependencies.push((*export_dep, *module_id));
399 }
400 }
401
402 (changed, dependencies)
403}
404
405struct ParsedExportSpec<'a> {
406 name: &'a Atom,
407 can_mangle: Option<bool>,
408 terminal_binding: bool,
409 exports: Option<&'a ExportSpecExports>,
410 from: Option<&'a ModuleGraphConnection>,
411 from_export: Option<&'a Nullable<Vec<Atom>>>,
412 priority: Option<u8>,
413 hidden: bool,
414 inlinable: Option<&'a EvaluatedInlinableValue>,
415}
416
417impl<'a> ParsedExportSpec<'a> {
418 pub fn new(
419 export_name_or_spec: &'a ExportNameOrSpec,
420 global_export_info: &'a DefaultExportInfo,
421 ) -> Self {
422 match export_name_or_spec {
423 ExportNameOrSpec::String(name) => Self {
424 name,
425 can_mangle: global_export_info.can_mangle,
426 terminal_binding: global_export_info.terminal_binding,
427 exports: None,
428 from: global_export_info.from,
429 from_export: None,
430 priority: global_export_info.priority,
431 hidden: false,
432 inlinable: None,
433 },
434 ExportNameOrSpec::ExportSpec(spec) => Self {
435 name: &spec.name,
436 can_mangle: spec.can_mangle.or(global_export_info.can_mangle),
437 terminal_binding: spec
438 .terminal_binding
439 .unwrap_or(global_export_info.terminal_binding),
440 exports: spec.exports.as_ref(),
441 from: spec.from.as_ref().or(global_export_info.from),
442 from_export: spec.export.as_ref(),
443 priority: spec.priority.or(global_export_info.priority),
444 hidden: spec.hidden.unwrap_or(false),
445 inlinable: spec.inlinable.as_ref(),
446 },
447 }
448 }
449}
450
451pub fn merge_exports_without_nested(
456 mg: &ModuleGraph,
457 module_id: &ModuleIdentifier,
458 exports_info: &mut ExportsInfoData,
459 exports: &Vec<ExportNameOrSpec>,
460 global_export_info: DefaultExportInfo,
461 dep_id: DependencyId,
462) -> (bool, Vec<(ModuleIdentifier, ModuleIdentifier)>) {
463 let mut changed = false;
464 let mut dependencies = vec![];
465 for export_name_or_spec in exports {
466 let ParsedExportSpec {
467 name,
468 can_mangle,
469 terminal_binding,
470 from,
471 from_export,
472 priority,
473 hidden,
474 inlinable,
475 ..
476 } = ParsedExportSpec::new(export_name_or_spec, &global_export_info);
477
478 let export_info = exports_info.ensure_owned_export_info(name);
479 changed |= set_export_base_info(export_info, can_mangle, terminal_binding, inlinable);
480
481 changed |= set_export_target(
482 export_info,
483 from,
484 from_export,
485 priority,
486 hidden,
487 dep_id,
488 name,
489 );
490
491 let (target_exports_info, target_dependencies) =
492 find_target_exports_info(mg, export_info, module_id);
493 dependencies.extend(target_dependencies);
494
495 if export_info.exports_info() != target_exports_info {
496 export_info.set_exports_info(target_exports_info);
497 changed = true;
498 }
499 }
500 (changed, dependencies)
501}
502
503pub fn merge_exports(
507 mg: &mut ModuleGraph,
508 module_id: &ModuleIdentifier,
509 exports_info: ExportsInfo,
510 exports: &Vec<ExportNameOrSpec>,
511 global_export_info: DefaultExportInfo,
512 dep_id: DependencyId,
513) -> (bool, Vec<(ModuleIdentifier, ModuleIdentifier)>) {
514 let mut changed = false;
515 let mut dependencies = vec![];
516 for export_name_or_spec in exports {
517 let ParsedExportSpec {
518 name,
519 can_mangle,
520 terminal_binding,
521 exports,
522 from,
523 from_export,
524 priority,
525 hidden,
526 inlinable,
527 } = ParsedExportSpec::new(export_name_or_spec, &global_export_info);
528
529 let export_info = exports_info.as_data_mut(mg).ensure_export_info(name);
530 changed |= set_export_base_info(
531 export_info.as_data_mut(mg),
532 can_mangle,
533 terminal_binding,
534 inlinable,
535 );
536
537 if let Some(exports) = exports {
538 let (merge_changed, merge_dependencies) = merge_nested_exports(
539 mg,
540 module_id,
541 export_info.clone(),
542 exports,
543 global_export_info.clone(),
544 dep_id,
545 );
546 changed |= merge_changed;
547 dependencies.extend(merge_dependencies);
548 }
549
550 changed |= set_export_target(
551 export_info.as_data_mut(mg),
552 from,
553 from_export,
554 priority,
555 hidden,
556 dep_id,
557 name,
558 );
559
560 let (target_exports_info, target_dependencies) =
561 find_target_exports_info(mg, export_info.as_data(mg), module_id);
562 dependencies.extend(target_dependencies);
563
564 let export_info_data = export_info.as_data_mut(mg);
565 if export_info_data.exports_info_owned()
566 && export_info_data.exports_info() != target_exports_info
567 && let Some(target_exports_info) = target_exports_info
568 {
569 export_info_data.set_exports_info(Some(target_exports_info));
570 changed = true;
571 }
572 }
573 (changed, dependencies)
574}
575
576fn set_export_base_info(
577 export_info: &mut ExportInfoData,
578 can_mangle: Option<bool>,
579 terminal_binding: bool,
580 inlinable: Option<&EvaluatedInlinableValue>,
581) -> bool {
582 let mut changed = false;
583 if let Some(provided) = export_info.provided()
584 && matches!(
585 provided,
586 ExportProvided::NotProvided | ExportProvided::Unknown
587 )
588 {
589 export_info.set_provided(Some(ExportProvided::Provided));
590 changed = true;
591 }
592
593 if Some(false) != export_info.can_mangle_provide() && can_mangle == Some(false) {
594 export_info.set_can_mangle_provide(Some(false));
595 changed = true;
596 }
597
598 if let Some(inlined) = inlinable
599 && export_info.can_inline_provide().is_none()
600 {
601 export_info.set_can_inline_provide(Some(inlined.clone()));
602 changed = true;
603 }
604
605 if terminal_binding && !export_info.terminal_binding() {
606 export_info.set_terminal_binding(true);
607 changed = true;
608 }
609 changed
610}
611
612fn merge_nested_exports(
613 mg: &mut ModuleGraph,
614 module_id: &ModuleIdentifier,
615 export_info: ExportInfo,
616 exports: &ExportSpecExports,
617 global_export_info: DefaultExportInfo,
618 dep_id: DependencyId,
619) -> (bool, Vec<(ModuleIdentifier, ModuleIdentifier)>) {
620 let mut changed = false;
621 let mut dependencies = vec![];
622 let nested_exports_info = if export_info.as_data(mg).exports_info_owned() {
623 export_info
624 .as_data(mg)
625 .exports_info()
626 .expect("should have exports_info when exports_info is true")
627 } else {
628 let export_info = export_info.as_data_mut(mg);
629 let new_exports_info = ExportsInfoData::default();
630 let new_exports_info_id = new_exports_info.id();
631 export_info.set_exports_info(Some(new_exports_info_id));
632 export_info.set_exports_info_owned(true);
633 mg.set_exports_info(new_exports_info_id, new_exports_info);
634
635 new_exports_info_id.as_data_mut(mg).set_has_provide_info();
636 new_exports_info_id
637 };
638
639 if exports.unknown_provided {
640 nested_exports_info
641 .as_data_mut(mg)
642 .set_unknown_exports_provided(false, None, None, None, None);
643 }
644
645 let (merge_changed, merge_dependencies) = merge_exports(
646 mg,
647 module_id,
648 nested_exports_info,
649 &exports.exports,
650 global_export_info.clone(),
651 dep_id,
652 );
653 changed |= merge_changed;
654 dependencies.extend(merge_dependencies);
655
656 (changed, dependencies)
657}
658
659fn set_export_target(
660 export_info: &mut ExportInfoData,
661 from: Option<&ModuleGraphConnection>,
662 from_export: Option<&Nullable<Vec<Atom>>>,
663 priority: Option<u8>,
664 hidden: bool,
665 dep_id: DependencyId,
666 name: &Atom,
667) -> bool {
668 let mut changed = false;
669 if let Some(from) = from {
672 changed |= if hidden {
673 export_info.unset_target(&dep_id)
674 } else {
675 let fallback = rspack_core::Nullable::Value(vec![name.clone()]);
676 let export_name = if let Some(from) = from_export {
677 Some(from)
678 } else {
679 Some(&fallback)
680 };
681 export_info.set_target(
682 Some(dep_id),
683 Some(from.dependency_id),
684 export_name,
685 priority,
686 )
687 }
688 }
689 changed
690}
691
692fn find_target_exports_info(
693 mg: &ModuleGraph,
694 export_info: &ExportInfoData,
695 module_id: &ModuleIdentifier,
696) -> (
697 Option<ExportsInfo>,
698 Vec<(ModuleIdentifier, ModuleIdentifier)>,
699) {
700 let mut dependencies = vec![];
701 let target = get_target(export_info, mg, Rc::new(|_| true), &mut Default::default());
703
704 let mut target_exports_info = None;
705 if let Some(GetTargetResult::Target(target)) = target {
706 let target_module_exports_info = mg.get_prefetched_exports_info(
707 &target.module,
708 if let Some(names) = &target.export {
709 PrefetchExportsInfoMode::Nested(names)
710 } else {
711 PrefetchExportsInfoMode::Default
712 },
713 );
714 target_exports_info = target_module_exports_info
715 .get_nested_exports_info(target.export.as_deref())
716 .map(|data| data.id());
717
718 dependencies.push((target.module, *module_id));
719 }
720
721 (target_exports_info, dependencies)
722}