diff --git a/crates/rspack_binding_api/src/plugins/interceptor.rs b/crates/rspack_binding_api/src/plugins/interceptor.rs index 3ccfb979c1c6..f0e59bca4589 100644 --- a/crates/rspack_binding_api/src/plugins/interceptor.rs +++ b/crates/rspack_binding_api/src/plugins/interceptor.rs @@ -1264,6 +1264,7 @@ impl CompilationFinishModules for CompilationFinishModulesTap { compilation: &Compilation, _async_modules_artifact: &mut AsyncModulesArtifact, exports_info_artifact: &mut rspack_core::ExportsInfoArtifact, + _dependency_exports_analysis_artifact: &mut rspack_core::DependencyExportsAnalysisArtifact, ) -> rspack_error::Result<()> { let compiler_context = compilation.compiler_context.clone(); let previous_ptr_addr = compiler_context diff --git a/crates/rspack_core/src/artifacts/dependency_exports_analysis_artifact.rs b/crates/rspack_core/src/artifacts/dependency_exports_analysis_artifact.rs new file mode 100644 index 000000000000..49a247b764bd --- /dev/null +++ b/crates/rspack_core/src/artifacts/dependency_exports_analysis_artifact.rs @@ -0,0 +1,698 @@ +use std::{collections::BTreeSet, sync::Arc}; + +use rspack_collections::{IdentifierMap, IdentifierSet}; + +use crate::{ + ArtifactExt, DeferredReexportSpec, DependencyId, ExportsSpec, ModuleIdentifier, + incremental::{Incremental, IncrementalPasses}, +}; + +#[derive(Debug, Default)] +pub struct ModuleDependencyExportsAnalysis { + dirty: bool, + dependency_ids: Arc<[DependencyId]>, + fully_static: bool, + targets: Vec, + flat_dependency_targets: Vec, + flat_local_apply: Vec<(DependencyId, ExportsSpec)>, + structured_local_apply: Vec<(DependencyId, ExportsSpec)>, + deferred_reexports: Vec, +} + +impl ModuleDependencyExportsAnalysis { + pub fn dirty(&self) -> bool { + self.dirty + } + + pub fn set_dirty(&mut self, dirty: bool) { + self.dirty = dirty; + } + + pub fn with_targets(targets: impl IntoIterator) -> Self { + Self { + targets: targets.into_iter().collect(), + ..Default::default() + } + } + + pub fn with_staged_analysis( + dependency_ids: Arc<[DependencyId]>, + fully_static: bool, + targets: impl IntoIterator, + flat_dependency_targets: impl IntoIterator, + flat_local_apply: impl IntoIterator, + structured_local_apply: impl IntoIterator, + deferred_reexports: impl IntoIterator, + ) -> Self { + Self { + dependency_ids, + fully_static, + targets: targets.into_iter().collect(), + flat_dependency_targets: flat_dependency_targets.into_iter().collect(), + flat_local_apply: flat_local_apply.into_iter().collect(), + structured_local_apply: structured_local_apply.into_iter().collect(), + deferred_reexports: deferred_reexports.into_iter().collect(), + ..Default::default() + } + } + + pub fn flat_local_apply(&self) -> &[(DependencyId, ExportsSpec)] { + &self.flat_local_apply + } + + pub fn dependency_ids(&self) -> &[DependencyId] { + &self.dependency_ids + } + + pub fn dependency_ids_arc(&self) -> &Arc<[DependencyId]> { + &self.dependency_ids + } + + pub fn fully_static(&self) -> bool { + self.fully_static + } + + pub fn targets(&self) -> &[ModuleIdentifier] { + &self.targets + } + + pub fn flat_dependency_targets(&self) -> &[ModuleIdentifier] { + &self.flat_dependency_targets + } + + pub fn structured_local_apply(&self) -> &[(DependencyId, ExportsSpec)] { + &self.structured_local_apply + } + + pub fn deferred_reexports(&self) -> &[DeferredReexportSpec] { + &self.deferred_reexports + } + + pub fn can_reuse_without_recollect(&self) -> bool { + self.targets.is_empty() + && self.structured_local_apply.is_empty() + && self.deferred_reexports.is_empty() + && self.flat_local_apply.iter().all(|(_, exports_spec)| { + exports_spec.from.is_none() + && exports_spec + .dependencies + .as_ref() + .is_none_or(|dependencies| dependencies.is_empty()) + }) + } + + pub fn can_reuse_collected_exports(&self) -> bool { + self.fully_static + } +} + +#[derive(Debug, Default, Clone)] +struct DependencyExportsSccNode { + modules: Vec, + incoming_sccs: Vec, + outgoing_sccs: Vec, + has_deferred_reexports: bool, +} + +#[derive(Debug, Default, Clone)] +pub struct DependencyExportsTopology { + scc_nodes: Vec, + waves: Vec>, + wave_modules: Vec, + deferred_waves: Vec>, +} + +#[derive(Debug, Default)] +pub struct DependencyExportsAnalysisArtifact { + modules: IdentifierMap, + flat_target_dependents: IdentifierMap, + topology: DependencyExportsTopology, + topology_dirty: bool, +} + +impl ArtifactExt for DependencyExportsAnalysisArtifact { + const PASS: IncrementalPasses = IncrementalPasses::FINISH_MODULES; + + fn recover(incremental: &Incremental, new: &mut Self, old: &mut Self) { + if incremental.mutations_readable(Self::PASS) { + std::mem::swap(new, old); + new.mark_all_dirty(); + new.set_topology_dirty(true); + } + } +} + +impl DependencyExportsAnalysisArtifact { + pub fn modules(&self) -> &IdentifierMap { + &self.modules + } + + pub fn module( + &self, + module_identifier: &ModuleIdentifier, + ) -> Option<&ModuleDependencyExportsAnalysis> { + self.modules.get(module_identifier) + } + + pub fn flat_dependents_of(&self, module_identifier: &ModuleIdentifier) -> Option<&IdentifierSet> { + self.flat_target_dependents.get(module_identifier) + } + + pub fn module_mut( + &mut self, + module_identifier: &ModuleIdentifier, + ) -> Option<&mut ModuleDependencyExportsAnalysis> { + self.modules.get_mut(module_identifier) + } + + pub fn dirty_modules(&self) -> IdentifierSet { + self + .modules + .iter() + .filter_map(|(module_identifier, analysis)| analysis.dirty().then_some(*module_identifier)) + .collect() + } + + pub fn clear_all_dirty(&mut self) { + self + .modules + .values_mut() + .for_each(|analysis| analysis.set_dirty(false)); + } + + pub fn has_deferred_reexports(&self) -> bool { + self + .modules + .values() + .any(|analysis| !analysis.deferred_reexports().is_empty()) + } + + pub fn rebuild_topology(&mut self) { + self.topology = DependencyExportsTopology::from_modules(&self.modules); + self.topology_dirty = false; + } + + pub fn replace_module( + &mut self, + module_identifier: ModuleIdentifier, + analysis: ModuleDependencyExportsAnalysis, + ) -> Option { + let previous = self.modules.insert(module_identifier, analysis); + self.update_flat_target_dependents(module_identifier, previous.as_ref()); + self.set_topology_dirty(true); + previous + } + + pub fn upsert_module( + &mut self, + module_identifier: ModuleIdentifier, + analysis: ModuleDependencyExportsAnalysis, + ) -> Option { + let previous = self.modules.insert(module_identifier, analysis); + self.update_flat_target_dependents(module_identifier, previous.as_ref()); + if previous + .as_ref() + .is_none_or(|previous| previous.targets() != self.modules[&module_identifier].targets()) + { + self.set_topology_dirty(true); + } + previous + } + + pub fn remove_module( + &mut self, + module_identifier: &ModuleIdentifier, + ) -> Option { + let previous = self.modules.remove(module_identifier); + if previous.is_some() { + self.remove_flat_target_dependents(*module_identifier, previous.as_ref()); + self.set_topology_dirty(true); + } + previous + } + + fn mark_all_dirty(&mut self) { + self + .modules + .values_mut() + .for_each(|analysis| analysis.set_dirty(true)); + } + + pub fn topology_dirty(&self) -> bool { + self.topology_dirty + } + + pub fn topology(&self) -> &DependencyExportsTopology { + &self.topology + } + + fn set_topology_dirty(&mut self, topology_dirty: bool) { + self.topology_dirty = topology_dirty; + } + + fn update_flat_target_dependents( + &mut self, + module_identifier: ModuleIdentifier, + previous: Option<&ModuleDependencyExportsAnalysis>, + ) { + self.remove_flat_target_dependents(module_identifier, previous); + if let Some(analysis) = self.modules.get(&module_identifier) { + for target in analysis.flat_dependency_targets() { + self + .flat_target_dependents + .entry(*target) + .or_default() + .insert(module_identifier); + } + } + } + + fn remove_flat_target_dependents( + &mut self, + module_identifier: ModuleIdentifier, + previous: Option<&ModuleDependencyExportsAnalysis>, + ) { + let Some(previous) = previous else { + return; + }; + for target in previous.flat_dependency_targets() { + let should_remove_entry = + if let Some(dependents) = self.flat_target_dependents.get_mut(target) { + dependents.remove(&module_identifier); + dependents.is_empty() + } else { + false + }; + if should_remove_entry { + self.flat_target_dependents.remove(target); + } + } + } +} + +impl DependencyExportsTopology { + fn from_modules(modules: &IdentifierMap) -> Self { + let scc = compute_strongly_connected_components(modules); + let scc_nodes = condense_scc_graph(modules, &scc); + let waves = build_parallel_waves(&scc_nodes); + let wave_modules = waves + .iter() + .map(|wave| { + wave + .iter() + .flat_map(|scc_id| scc_nodes[*scc_id].modules.iter().copied()) + .collect() + }) + .collect(); + let deferred_waves = waves + .iter() + .map(|wave| { + wave + .iter() + .copied() + .filter(|scc_id| scc_nodes[*scc_id].has_deferred_reexports) + .collect() + }) + .collect(); + + Self { + scc_nodes, + waves, + wave_modules, + deferred_waves, + } + } + + pub fn scc_modules(&self, scc_id: usize) -> &[ModuleIdentifier] { + self + .scc_nodes + .get(scc_id) + .map_or(&[], |node| node.modules.as_slice()) + } + + pub fn waves(&self) -> &[Vec] { + &self.waves + } + + pub fn wave_modules(&self, wave_index: usize) -> &IdentifierSet { + &self.wave_modules[wave_index] + } + + pub fn deferred_wave(&self, wave_index: usize) -> &[usize] { + &self.deferred_waves[wave_index] + } +} + +#[derive(Debug)] +struct StronglyConnectedComponents { + module_to_scc: IdentifierMap, + scc_modules: Vec>, +} + +#[derive(Debug)] +struct DfsFrame { + module: ModuleIdentifier, + next_neighbor_index: usize, +} + +fn compute_strongly_connected_components( + modules: &IdentifierMap, +) -> StronglyConnectedComponents { + let module_graph = build_module_graph(modules); + let reverse_module_graph = build_reverse_module_graph(&module_graph); + let mut finish_order = build_finish_order(&module_graph); + let mut visited = BTreeSet::new(); + let mut scc_modules = Vec::new(); + + while let Some(module_identifier) = finish_order.pop() { + if !visited.insert(module_identifier) { + continue; + } + + let mut component = Vec::new(); + let mut stack = vec![module_identifier]; + while let Some(module_identifier) = stack.pop() { + component.push(module_identifier); + + for neighbor in reverse_module_graph + .get(&module_identifier) + .map_or(&[][..], Vec::as_slice) + .iter() + .rev() + { + if visited.insert(*neighbor) { + stack.push(*neighbor); + } + } + } + + component.sort_unstable(); + scc_modules.push(component); + } + + scc_modules.sort_unstable(); + let mut module_to_scc = IdentifierMap::default(); + for (scc_id, modules) in scc_modules.iter().enumerate() { + for module_identifier in modules { + module_to_scc.insert(*module_identifier, scc_id); + } + } + + StronglyConnectedComponents { + module_to_scc, + scc_modules, + } +} + +fn build_module_graph( + modules: &IdentifierMap, +) -> IdentifierMap> { + let mut module_identifiers = modules.keys().copied().collect::>(); + module_identifiers.sort_unstable(); + + module_identifiers + .into_iter() + .map(|module_identifier| { + ( + module_identifier, + module_targets(modules, module_identifier), + ) + }) + .collect() +} + +fn build_reverse_module_graph( + module_graph: &IdentifierMap>, +) -> IdentifierMap> { + let mut reverse_module_graph = IdentifierMap::default(); + let mut module_identifiers = module_graph.keys().copied().collect::>(); + module_identifiers.sort_unstable(); + + for module_identifier in &module_identifiers { + reverse_module_graph.insert(*module_identifier, Vec::new()); + } + + for module_identifier in module_identifiers { + for target in &module_graph[&module_identifier] { + reverse_module_graph + .get_mut(target) + .expect("reverse graph should contain every target module") + .push(module_identifier); + } + } + + reverse_module_graph.values_mut().for_each(|neighbors| { + neighbors.sort_unstable(); + neighbors.dedup(); + }); + + reverse_module_graph +} + +fn build_finish_order( + module_graph: &IdentifierMap>, +) -> Vec { + let mut module_identifiers = module_graph.keys().copied().collect::>(); + module_identifiers.sort_unstable(); + let mut visited = BTreeSet::new(); + let mut finish_order = Vec::with_capacity(module_identifiers.len()); + + for module_identifier in module_identifiers { + if !visited.insert(module_identifier) { + continue; + } + + let mut stack = vec![DfsFrame { + module: module_identifier, + next_neighbor_index: 0, + }]; + + while let Some(frame) = stack.last_mut() { + let neighbors = module_graph + .get(&frame.module) + .map_or(&[][..], Vec::as_slice); + + if frame.next_neighbor_index < neighbors.len() { + let neighbor = neighbors[frame.next_neighbor_index]; + frame.next_neighbor_index += 1; + if visited.insert(neighbor) { + stack.push(DfsFrame { + module: neighbor, + next_neighbor_index: 0, + }); + } + } else { + let frame = stack.pop().expect("last frame should exist"); + finish_order.push(frame.module); + } + } + } + + finish_order +} + +fn condense_scc_graph( + modules: &IdentifierMap, + scc: &StronglyConnectedComponents, +) -> Vec { + let mut scc_nodes = scc + .scc_modules + .iter() + .map(|scc_modules| DependencyExportsSccNode { + modules: scc_modules.clone(), + incoming_sccs: Vec::new(), + outgoing_sccs: Vec::new(), + has_deferred_reexports: scc_modules.iter().any(|module_identifier| { + modules + .get(module_identifier) + .is_some_and(|analysis| !analysis.deferred_reexports().is_empty()) + }), + }) + .collect::>(); + let mut incoming_edges = vec![BTreeSet::new(); scc_nodes.len()]; + let mut outgoing_edges = vec![BTreeSet::new(); scc_nodes.len()]; + let mut module_identifiers = modules.keys().copied().collect::>(); + module_identifiers.sort_unstable(); + + for module_identifier in module_identifiers { + let source_scc = scc.module_to_scc[&module_identifier]; + for target in module_targets(modules, module_identifier) { + let target_scc = scc.module_to_scc[&target]; + if source_scc == target_scc { + continue; + } + outgoing_edges[source_scc].insert(target_scc); + incoming_edges[target_scc].insert(source_scc); + } + } + + for (scc_id, node) in scc_nodes.iter_mut().enumerate() { + node.incoming_sccs = incoming_edges[scc_id].iter().copied().collect(); + node.outgoing_sccs = outgoing_edges[scc_id].iter().copied().collect(); + } + + scc_nodes +} + +fn build_parallel_waves(scc_nodes: &[DependencyExportsSccNode]) -> Vec> { + let mut remaining_outgoing = scc_nodes + .iter() + .map(|node| node.outgoing_sccs.len()) + .collect::>(); + let mut processed = vec![false; scc_nodes.len()]; + let mut ready = remaining_outgoing + .iter() + .enumerate() + .filter_map(|(scc_id, outgoing_count)| (*outgoing_count == 0).then_some(scc_id)) + .collect::>(); + let mut waves = Vec::new(); + + while !ready.is_empty() { + let wave = ready.iter().copied().collect::>(); + ready.clear(); + + for scc_id in &wave { + processed[*scc_id] = true; + } + + for scc_id in &wave { + for incoming_scc in &scc_nodes[*scc_id].incoming_sccs { + if processed[*incoming_scc] { + continue; + } + remaining_outgoing[*incoming_scc] -= 1; + if remaining_outgoing[*incoming_scc] == 0 { + ready.insert(*incoming_scc); + } + } + } + + waves.push(wave); + } + + debug_assert!(processed.into_iter().all(|done| done)); + waves +} + +fn module_targets( + modules: &IdentifierMap, + module_identifier: ModuleIdentifier, +) -> Vec { + let mut targets = modules + .get(&module_identifier) + .map(|analysis| { + analysis + .targets + .iter() + .copied() + .filter(|target| modules.contains_key(target)) + .collect::>() + }) + .unwrap_or_default(); + + targets.sort_unstable(); + targets.dedup(); + targets +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + ModuleIdentifier, + incremental::{Incremental, IncrementalOptions, IncrementalPasses}, + }; + + fn topology_wave_modules( + topology: &DependencyExportsTopology, + ) -> Vec>> { + topology + .waves() + .iter() + .map(|wave| { + wave + .iter() + .map(|scc_id| topology.scc_modules(*scc_id).to_vec()) + .collect() + }) + .collect() + } + + #[test] + fn recover_keeps_previous_finish_modules_state_and_marks_it_dirty() { + let module = ModuleIdentifier::from("module-a"); + let mut old = DependencyExportsAnalysisArtifact::default(); + old.replace_module(module, ModuleDependencyExportsAnalysis::default()); + old.set_topology_dirty(false); + + let incremental = Incremental::new_hot(IncrementalOptions { + silent: true, + passes: IncrementalPasses::FINISH_MODULES, + }); + + let mut new = DependencyExportsAnalysisArtifact::default(); + DependencyExportsAnalysisArtifact::recover(&incremental, &mut new, &mut old); + + assert!(new.modules().contains_key(&module)); + assert!( + new + .modules() + .get(&module) + .expect("module should recover") + .dirty() + ); + assert!(new.topology_dirty()); + } + + #[test] + fn module_mutations_mark_topology_dirty() { + let module = ModuleIdentifier::from("module-a"); + let mut artifact = DependencyExportsAnalysisArtifact::default(); + + artifact.set_topology_dirty(false); + artifact.replace_module(module, ModuleDependencyExportsAnalysis::default()); + assert!(artifact.topology_dirty()); + + artifact.set_topology_dirty(false); + artifact.remove_module(&module); + assert!(artifact.topology_dirty()); + } + + #[test] + fn rebuild_topology_groups_independent_sccs_into_the_same_wave() { + let mut artifact = DependencyExportsAnalysisArtifact::default(); + let a = ModuleIdentifier::from("a"); + let b = ModuleIdentifier::from("b"); + let y = ModuleIdentifier::from("y"); + let z = ModuleIdentifier::from("z"); + + artifact.replace_module(a, ModuleDependencyExportsAnalysis::with_targets([z])); + artifact.replace_module(b, ModuleDependencyExportsAnalysis::with_targets([y])); + artifact.replace_module(y, ModuleDependencyExportsAnalysis::default()); + artifact.replace_module(z, ModuleDependencyExportsAnalysis::default()); + + artifact.rebuild_topology(); + + assert_eq!( + topology_wave_modules(artifact.topology()), + vec![vec![vec![y], vec![z]], vec![vec![a], vec![b]]] + ); + } + + #[test] + fn rebuild_topology_puts_a_cycle_into_one_scc() { + let mut artifact = DependencyExportsAnalysisArtifact::default(); + let a = ModuleIdentifier::from("cycle-a"); + let b = ModuleIdentifier::from("cycle-b"); + + artifact.replace_module(a, ModuleDependencyExportsAnalysis::with_targets([b])); + artifact.replace_module(b, ModuleDependencyExportsAnalysis::with_targets([a])); + + artifact.rebuild_topology(); + + assert_eq!( + topology_wave_modules(artifact.topology()), + vec![vec![vec![a, b]]] + ); + } +} diff --git a/crates/rspack_core/src/artifacts/exports_info_artifact.rs b/crates/rspack_core/src/artifacts/exports_info_artifact.rs index 41e3422e8e33..0aef75cc654d 100644 --- a/crates/rspack_core/src/artifacts/exports_info_artifact.rs +++ b/crates/rspack_core/src/artifacts/exports_info_artifact.rs @@ -2,10 +2,11 @@ use rayon::prelude::*; use rspack_collections::IdentifierMap; use crate::{ - ArtifactExt, ExportsInfo, ExportsInfoData, ExportsInfoGetter, ModuleIdentifier, + ArtifactExt, ExportsInfo, ExportsInfoData, ExportsInfoGetter, ExportsInfoRead, ModuleIdentifier, PrefetchExportsInfoMode, PrefetchedExportsInfoUsed, PrefetchedExportsInfoWrapper, RuntimeSpec, incremental::{Incremental, IncrementalPasses}, module_graph::rollback, + utils::StealCell, }; #[derive(Debug, Default)] @@ -139,6 +140,26 @@ impl ExportsInfoArtifact { } } +impl ExportsInfoRead for ExportsInfoArtifact { + fn get_exports_info(&self, module_identifier: &ModuleIdentifier) -> ExportsInfo { + ExportsInfoArtifact::get_exports_info(self, module_identifier) + } + + fn get_exports_info_by_id(&self, id: &ExportsInfo) -> &ExportsInfoData { + ExportsInfoArtifact::get_exports_info_by_id(self, id) + } +} + +impl ExportsInfoRead for StealCell { + fn get_exports_info(&self, module_identifier: &ModuleIdentifier) -> ExportsInfo { + (**self).get_exports_info(module_identifier) + } + + fn get_exports_info_by_id(&self, id: &ExportsInfo) -> &ExportsInfoData { + (**self).get_exports_info_by_id(id) + } +} + impl Extend<(ExportsInfo, ExportsInfoData)> for ExportsInfoArtifact { fn extend>(&mut self, iter: T) { for (id, info) in iter { diff --git a/crates/rspack_core/src/artifacts/mod.rs b/crates/rspack_core/src/artifacts/mod.rs index 6810b53b5418..859785d1abd9 100644 --- a/crates/rspack_core/src/artifacts/mod.rs +++ b/crates/rspack_core/src/artifacts/mod.rs @@ -11,6 +11,7 @@ mod chunk_render_cache_artifact; mod code_generate_cache_artifact; mod code_generation_results; mod dependencies_diagnostics_artifact; +mod dependency_exports_analysis_artifact; mod exports_info_artifact; mod imported_by_defer_modules_artifact; mod module_graph_cache_artifact; @@ -94,6 +95,7 @@ pub use chunk_render_cache_artifact::ChunkRenderCacheArtifact; pub use code_generate_cache_artifact::CodeGenerateCacheArtifact; pub use code_generation_results::*; pub use dependencies_diagnostics_artifact::DependenciesDiagnosticsArtifact; +pub use dependency_exports_analysis_artifact::*; pub use exports_info_artifact::ExportsInfoArtifact; pub use imported_by_defer_modules_artifact::ImportedByDeferModulesArtifact; pub use module_graph_cache_artifact::*; diff --git a/crates/rspack_core/src/artifacts/module_graph_cache_artifact.rs b/crates/rspack_core/src/artifacts/module_graph_cache_artifact.rs index ae88679d2ad7..87ee2d3c5970 100644 --- a/crates/rspack_core/src/artifacts/module_graph_cache_artifact.rs +++ b/crates/rspack_core/src/artifacts/module_graph_cache_artifact.rs @@ -386,6 +386,7 @@ pub struct ExportModeUnused { #[derive(Debug, Clone)] pub struct ExportModeEmptyStar { + pub ignored_exports: HashSet, pub hidden: Option>, } @@ -421,6 +422,8 @@ pub struct ExportModeReexportUndefined { #[derive(Debug, Clone)] pub struct ExportModeNormalReexport { pub items: Vec, + pub is_star_reexport: bool, + pub ignored_exports: HashSet, } #[derive(Debug, Clone)] diff --git a/crates/rspack_core/src/cache/memory.rs b/crates/rspack_core/src/cache/memory.rs index 4dfa0ec19fe0..d21922128622 100644 --- a/crates/rspack_core/src/cache/memory.rs +++ b/crates/rspack_core/src/cache/memory.rs @@ -41,7 +41,8 @@ impl Cache for MemoryCache { } } - // FINISH_MODULES: async_modules_artifact, dependencies_diagnostics_artifact + // FINISH_MODULES: async_modules_artifact, dependencies_diagnostics_artifact, + // dependency_exports_analysis_artifact async fn before_finish_modules(&mut self, compilation: &mut Compilation) { if let Some(old_compilation) = self.old_compilation.as_mut() { let incremental = &compilation.incremental; @@ -55,6 +56,11 @@ impl Cache for MemoryCache { &mut compilation.dependencies_diagnostics_artifact, &mut old_compilation.dependencies_diagnostics_artifact, ); + recover_artifact( + incremental, + &mut compilation.dependency_exports_analysis_artifact, + &mut old_compilation.dependency_exports_analysis_artifact, + ); } } diff --git a/crates/rspack_core/src/compilation/finish_modules/mod.rs b/crates/rspack_core/src/compilation/finish_modules/mod.rs index ca976fc7cecd..087a5c185442 100644 --- a/crates/rspack_core/src/compilation/finish_modules/mod.rs +++ b/crates/rspack_core/src/compilation/finish_modules/mod.rs @@ -38,16 +38,20 @@ pub async fn finish_modules_pass(compilation: &mut Compilation) -> Result<()> { let mut dependencies_diagnostics_artifact = compilation.dependencies_diagnostics_artifact.steal(); let mut async_modules_artifact = compilation.async_modules_artifact.steal(); let mut exports_info_artifact = compilation.exports_info_artifact.steal(); + let mut dependency_exports_analysis_artifact = + compilation.dependency_exports_analysis_artifact.steal(); let diagnostics = finish_modules_inner( compilation, &mut dependencies_diagnostics_artifact, &mut async_modules_artifact, &mut exports_info_artifact, + &mut dependency_exports_analysis_artifact, ) .await; compilation.dependencies_diagnostics_artifact = dependencies_diagnostics_artifact.into(); compilation.async_modules_artifact = async_modules_artifact.into(); compilation.exports_info_artifact = exports_info_artifact.into(); + compilation.dependency_exports_analysis_artifact = dependency_exports_analysis_artifact.into(); let diagnostics = diagnostics?; compilation.extend_diagnostics(diagnostics); @@ -60,6 +64,7 @@ pub async fn finish_modules_inner( dependencies_diagnostics_artifact: &mut DependenciesDiagnosticsArtifact, async_modules_artifact: &mut AsyncModulesArtifact, exports_info_artifact: &mut ExportsInfoArtifact, + dependency_exports_analysis_artifact: &mut DependencyExportsAnalysisArtifact, ) -> Result> { let build_module_graph_artifact = &compilation.build_module_graph_artifact; if let Some(mut mutations) = compilation.incremental.mutations_write() { @@ -103,7 +108,12 @@ pub async fn finish_modules_inner( .clone() .compilation_hooks .finish_modules - .call(compilation, async_modules_artifact, exports_info_artifact) + .call( + compilation, + async_modules_artifact, + exports_info_artifact, + dependency_exports_analysis_artifact, + ) .await?; // https://github.com/webpack/webpack/blob/19ca74127f7668aaf60d59f4af8fcaee7924541a/lib/Compilation.js#L2988 diff --git a/crates/rspack_core/src/compilation/mod.rs b/crates/rspack_core/src/compilation/mod.rs index 930f321ebaf2..3f4b35efcac3 100644 --- a/crates/rspack_core/src/compilation/mod.rs +++ b/crates/rspack_core/src/compilation/mod.rs @@ -67,14 +67,15 @@ use crate::{ ChunkRenderCacheArtifact, ChunkRenderResult, ChunkUkey, CodeGenerateCacheArtifact, CodeGenerationJob, CodeGenerationResult, CodeGenerationResults, CompilationLogger, CompilationLogging, CompilerOptions, CompilerPlatform, ConcatenationScope, - DependenciesDiagnosticsArtifact, DependencyId, DependencyTemplate, DependencyTemplateType, - DependencyType, Entry, EntryData, EntryOptions, EntryRuntime, Entrypoint, ExecuteModuleId, - ExportsInfoArtifact, ExtendedReferencedExport, Filename, ImportPhase, ImportVarMap, - ImportedByDeferModulesArtifact, MemoryGCStorage, ModuleFactory, ModuleGraph, - ModuleGraphCacheArtifact, ModuleIdentifier, ModuleIdsArtifact, ModuleStaticCache, PathData, - ProcessRuntimeRequirementsCacheArtifact, ResolverFactory, RuntimeGlobals, RuntimeKeyMap, - RuntimeMode, RuntimeModule, RuntimeSpec, RuntimeSpecMap, RuntimeTemplate, SharedPluginDriver, - SideEffectsOptimizeArtifact, SourceType, Stats, StatsContext, StealCell, ValueCacheVersions, + DependenciesDiagnosticsArtifact, DependencyExportsAnalysisArtifact, DependencyId, + DependencyTemplate, DependencyTemplateType, DependencyType, Entry, EntryData, EntryOptions, + EntryRuntime, Entrypoint, ExecuteModuleId, ExportsInfoArtifact, ExtendedReferencedExport, + Filename, ImportPhase, ImportVarMap, ImportedByDeferModulesArtifact, MemoryGCStorage, + ModuleFactory, ModuleGraph, ModuleGraphCacheArtifact, ModuleIdentifier, ModuleIdsArtifact, + ModuleStaticCache, PathData, ProcessRuntimeRequirementsCacheArtifact, ResolverFactory, + RuntimeGlobals, RuntimeKeyMap, RuntimeMode, RuntimeModule, RuntimeSpec, RuntimeSpecMap, + RuntimeTemplate, SharedPluginDriver, SideEffectsOptimizeArtifact, SourceType, Stats, + StatsContext, StealCell, ValueCacheVersions, compilation::build_module_graph::{ BuildModuleGraphArtifact, ModuleExecutor, UpdateParam, update_module_graph, }, @@ -91,7 +92,7 @@ define_hook!(CompilationStillValidModule: Series(compiler_id: CompilerId, compil define_hook!(CompilationSucceedModule: Series(compiler_id: CompilerId, compilation_id: CompilationId, module: &mut BoxModule),tracing=false); define_hook!(CompilationExecuteModule: Series(module: &ModuleIdentifier, runtime_modules: &IdentifierSet, code_generation_results: &BindingCell, execute_module_id: &ExecuteModuleId)); -define_hook!(CompilationFinishModules: Series(compilation: &Compilation, async_modules_artifact: &mut AsyncModulesArtifact, exports_info_artifact: &mut ExportsInfoArtifact)); +define_hook!(CompilationFinishModules: Series(compilation: &Compilation, async_modules_artifact: &mut AsyncModulesArtifact, exports_info_artifact: &mut ExportsInfoArtifact, dependency_exports_analysis_artifact: &mut DependencyExportsAnalysisArtifact)); define_hook!(CompilationSeal: Series(compilation: &Compilation, diagnostics: &mut Vec)); define_hook!(CompilationDependencyReferencedExports: Sync( compilation: &Compilation, @@ -228,6 +229,8 @@ pub struct Compilation { pub dependencies_diagnostics_artifact: StealCell, // artifact for exports info pub exports_info_artifact: StealCell, + // artifact for dependency exports analysis + pub dependency_exports_analysis_artifact: StealCell, // artifact for side_effects_flag_plugin pub side_effects_optimize_artifact: StealCell, // artifact for module_ids @@ -365,6 +368,7 @@ impl Compilation { imported_by_defer_modules_artifact: StealCell::new(Default::default()), dependencies_diagnostics_artifact: StealCell::new(DependenciesDiagnosticsArtifact::default()), exports_info_artifact: StealCell::new(ExportsInfoArtifact::default()), + dependency_exports_analysis_artifact: StealCell::new(Default::default()), side_effects_optimize_artifact: StealCell::new(Default::default()), module_ids_artifact: StealCell::new(Default::default()), named_chunk_ids_artifact: StealCell::new(Default::default()), diff --git a/crates/rspack_core/src/dependency/dependency_trait.rs b/crates/rspack_core/src/dependency/dependency_trait.rs index 17cbec24d443..d01f7d0b768a 100644 --- a/crates/rspack_core/src/dependency/dependency_trait.rs +++ b/crates/rspack_core/src/dependency/dependency_trait.rs @@ -24,6 +24,12 @@ pub enum AffectType { Transitive, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GetExportsCacheability { + Dynamic, + Static, +} + #[cacheable_dyn] pub trait Dependency: AsDependencyCodeGeneration @@ -72,6 +78,14 @@ pub trait Dependency: None } + fn get_exports_cacheability( + &self, + _mg: &ModuleGraph, + _module_graph_cache: &ModuleGraphCacheArtifact, + ) -> GetExportsCacheability { + GetExportsCacheability::Dynamic + } + fn get_module_evaluation_side_effects_state( &self, _module_graph: &ModuleGraph, diff --git a/crates/rspack_core/src/dependency/mod.rs b/crates/rspack_core/src/dependency/mod.rs index 015e744da2c9..e73cac15af85 100644 --- a/crates/rspack_core/src/dependency/mod.rs +++ b/crates/rspack_core/src/dependency/mod.rs @@ -69,7 +69,7 @@ pub struct ExportSpec { pub inlinable: Option, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Nullable { Null, Value(T), @@ -104,10 +104,83 @@ pub enum ExportsOfExportsSpec { Names(Vec), } +#[derive(Debug, Clone, Default)] +pub enum ExportsProcessing { + #[default] + Immediate, + DeferredReexport(Vec), +} + +#[derive(Debug, Clone)] +pub struct DeferredReexportSpec { + pub target_module: ModuleIdentifier, + pub dep_id: DependencyId, + pub priority: Option, + pub can_mangle: Option, + pub terminal_binding: bool, + pub items: Vec, + pub star_exports: Option, +} + +impl DeferredReexportSpec { + pub fn new( + target_module: ModuleIdentifier, + dep_id: DependencyId, + items: Vec, + ) -> Self { + Self { + target_module, + dep_id, + priority: None, + can_mangle: None, + terminal_binding: false, + items, + star_exports: None, + } + } + + pub fn new_star( + target_module: ModuleIdentifier, + dep_id: DependencyId, + export_name_prefix: Vec, + ignored_exports: FxHashSet, + hidden_exports: FxHashSet, + ) -> Self { + Self { + target_module, + dep_id, + priority: None, + can_mangle: None, + terminal_binding: false, + items: Vec::new(), + star_exports: Some(DeferredStarReexport { + export_name_prefix, + ignored_exports, + hidden_exports, + }), + } + } +} + +#[derive(Debug, Clone)] +pub struct DeferredReexportItem { + pub exposed_name: Atom, + pub target_path: Nullable>, + pub hidden: bool, +} + +#[derive(Debug, Clone)] +pub struct DeferredStarReexport { + pub export_name_prefix: Vec, + pub ignored_exports: FxHashSet, + pub hidden_exports: FxHashSet, +} + #[derive(Debug, Default)] #[allow(unused)] pub struct ExportsSpec { pub exports: ExportsOfExportsSpec, + pub processing: ExportsProcessing, pub priority: Option, pub can_mangle: Option, pub terminal_binding: Option, diff --git a/crates/rspack_core/src/dependency/static_exports_dependency.rs b/crates/rspack_core/src/dependency/static_exports_dependency.rs index fcfe787d4d70..72dfa9bc41cd 100644 --- a/crates/rspack_core/src/dependency/static_exports_dependency.rs +++ b/crates/rspack_core/src/dependency/static_exports_dependency.rs @@ -4,7 +4,7 @@ use rspack_cacheable::{ }; use swc_core::ecma::atoms::Atom; -use super::AffectType; +use super::{AffectType, GetExportsCacheability}; use crate::{ AsContextDependency, AsDependencyCodeGeneration, AsModuleDependency, Dependency, DependencyId, DependencyType, ExportNameOrSpec, ExportsInfoArtifact, ExportsOfExportsSpec, ExportsSpec, @@ -70,6 +70,14 @@ impl Dependency for StaticExportsDependency { fn could_affect_referencing_module(&self) -> AffectType { AffectType::True } + + fn get_exports_cacheability( + &self, + _mg: &ModuleGraph, + _mg_cache: &ModuleGraphCacheArtifact, + ) -> GetExportsCacheability { + GetExportsCacheability::Static + } } impl AsDependencyCodeGeneration for StaticExportsDependency {} diff --git a/crates/rspack_core/src/exports/exports_info_getter.rs b/crates/rspack_core/src/exports/exports_info_getter.rs index a149426b7afd..8c210888e32a 100644 --- a/crates/rspack_core/src/exports/exports_info_getter.rs +++ b/crates/rspack_core/src/exports/exports_info_getter.rs @@ -9,7 +9,8 @@ use super::{ ExportInfoData, ExportProvided, ExportsInfo, ProvidedExports, UsageState, UsedName, UsedNameItem, }; use crate::{ - ExportsInfoArtifact, ExportsInfoData, InlinedUsedName, RuntimeSpec, UsageKey, UsedExports, + ExportsInfoArtifact, ExportsInfoData, InlinedUsedName, ModuleIdentifier, RuntimeSpec, UsageKey, + UsedExports, }; #[derive(Debug, Clone)] @@ -19,6 +20,24 @@ pub enum PrefetchExportsInfoMode<'a> { Full, // prefetch with all related data, this should only be used in hash calculation } +pub trait ExportsInfoRead { + fn get_exports_info(&self, module_identifier: &ModuleIdentifier) -> ExportsInfo; + + fn get_exports_info_by_id(&self, id: &ExportsInfo) -> &ExportsInfoData; + + fn get_prefetched_exports_info<'a>( + &'a self, + module_identifier: &ModuleIdentifier, + mode: PrefetchExportsInfoMode<'a>, + ) -> PrefetchedExportsInfoWrapper<'a> + where + Self: Sized, + { + let exports_info = self.get_exports_info(module_identifier); + ExportsInfoGetter::prefetch(&exports_info, self, mode) + } +} + /** * Used to store data pre-fetched from Module Graph * so that subsequent exports data reads don't need to access Module Graph @@ -543,14 +562,14 @@ impl ExportsInfoGetter { * if names is provided, it will pre-fetch the exports info data of the export info items of specific names * if names is not provided, it will not pre-fetch any export info item */ - pub fn prefetch<'a>( + pub fn prefetch<'a, T: ExportsInfoRead + ?Sized>( id: &ExportsInfo, - exports_info_artifact: &'a ExportsInfoArtifact, + exports_info_artifact: &'a T, mode: PrefetchExportsInfoMode<'a>, ) -> PrefetchedExportsInfoWrapper<'a> { - fn prefetch_exports<'a>( + fn prefetch_exports<'a, T: ExportsInfoRead + ?Sized>( id: &ExportsInfo, - exports_info_artifact: &'a ExportsInfoArtifact, + exports_info_artifact: &'a T, res: &mut FxHashMap, mode: PrefetchExportsInfoMode<'a>, ) { @@ -558,7 +577,7 @@ impl ExportsInfoGetter { return; } - let exports_info = id.as_data(exports_info_artifact); + let exports_info = exports_info_artifact.get_exports_info_by_id(id); res.insert(*id, exports_info); match mode { @@ -611,7 +630,7 @@ impl ExportsInfoGetter { } let initial_capacity = { - let exports_info = id.as_data(exports_info_artifact); + let exports_info = exports_info_artifact.get_exports_info_by_id(id); let extra_nested_exports = usize::from(exports_info.other_exports_info().exports_info().is_some()) + usize::from( diff --git a/crates/rspack_core/src/exports/target.rs b/crates/rspack_core/src/exports/target.rs index 0f6f561e1294..16293fffd6a9 100644 --- a/crates/rspack_core/src/exports/target.rs +++ b/crates/rspack_core/src/exports/target.rs @@ -5,7 +5,7 @@ use rustc_hash::FxHashSet as HashSet; use crate::{ DependencyId, ExportInfo, ExportInfoData, ExportInfoHashKey, ExportsInfo, ExportsInfoArtifact, - ExportsInfoGetter, ModuleGraph, ModuleIdentifier, PrefetchExportsInfoMode, + ExportsInfoGetter, ExportsInfoRead, ModuleGraph, ModuleIdentifier, PrefetchExportsInfoMode, }; #[derive(Debug, Hash, Clone, PartialEq, Eq)] @@ -56,10 +56,10 @@ pub struct FindTargetResultItem { pub defer: bool, } -pub fn get_terminal_binding( +pub fn get_terminal_binding( export_info: &ExportInfoData, mg: &ModuleGraph, - exports_info_artifact: &ExportsInfoArtifact, + exports_info_artifact: &T, ) -> Option { if export_info.terminal_binding() { return Some(TerminalBinding::ExportInfo(export_info.id())); @@ -86,10 +86,10 @@ pub fn get_terminal_binding( .map(|data| TerminalBinding::ExportInfo(data.id())) } -pub fn find_target( +pub fn find_target( export_info: &ExportInfoData, mg: &ModuleGraph, - exports_info_artifact: &ExportsInfoArtifact, + exports_info_artifact: &T, valid_target_module_filter: Arc bool>, visited: &mut HashSet, ) -> FindTargetResult { @@ -168,10 +168,10 @@ pub fn find_target( } } -pub fn get_target( +pub fn get_target( export_info: &ExportInfoData, mg: &ModuleGraph, - exports_info_artifact: &ExportsInfoArtifact, + exports_info_artifact: &T, resolve_filter: &ResolveFilterFnTy<'_>, already_visited: &mut HashSet, ) -> Option { @@ -217,12 +217,12 @@ pub fn get_target( target } -fn resolve_target( +fn resolve_target( input_target: UnResolvedExportInfoTarget, already_visited: &mut HashSet, resolve_filter: &ResolveFilterFnTy<'_>, mg: &ModuleGraph, - exports_info_artifact: &ExportsInfoArtifact, + exports_info_artifact: &T, ) -> Option { let mut target = ResolvedExportInfoTarget { module: *input_target diff --git a/crates/rspack_plugin_esm_library/src/plugin.rs b/crates/rspack_plugin_esm_library/src/plugin.rs index b7d7b45a2749..f3ba9e540632 100644 --- a/crates/rspack_plugin_esm_library/src/plugin.rs +++ b/crates/rspack_plugin_esm_library/src/plugin.rs @@ -295,6 +295,7 @@ async fn finish_modules( compilation: &Compilation, _async_modules_artifact: &mut AsyncModulesArtifact, exports_info_artifact: &mut ExportsInfoArtifact, + _dependency_exports_analysis_artifact: &mut rspack_core::DependencyExportsAnalysisArtifact, ) -> Result<()> { let module_graph = compilation.get_module_graph(); let mut modules_map = IdentifierIndexMap::default(); diff --git a/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_export_require_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_export_require_dependency.rs index 003ea279a464..1be211d49b8c 100644 --- a/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_export_require_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_export_require_dependency.rs @@ -4,14 +4,15 @@ use rspack_cacheable::{ with::{AsPreset, AsVec}, }; use rspack_core::{ - AsContextDependency, Dependency, DependencyCategory, DependencyCodeGeneration, DependencyId, - DependencyRange, DependencyTemplate, DependencyTemplateType, DependencyType, ExportNameOrSpec, - ExportProvided, ExportSpec, ExportsInfoArtifact, ExportsInfoGetter, ExportsOfExportsSpec, - ExportsSpec, ExportsType, ExtendedReferencedExport, FactorizeInfo, GetUsedNameParam, - ModuleDependency, ModuleGraph, ModuleGraphCacheArtifact, ModuleIdentifier, Nullable, - PrefetchExportsInfoMode, ReferencedExport, RuntimeSpec, TemplateContext, TemplateReplaceSource, - UsageState, UsedName, collect_referenced_export_items, create_exports_object_referenced, - create_no_exports_referenced, property_access, to_normal_comment, + AsContextDependency, DeferredReexportItem, DeferredReexportSpec, Dependency, DependencyCategory, + DependencyCodeGeneration, DependencyId, DependencyRange, DependencyTemplate, + DependencyTemplateType, DependencyType, ExportNameOrSpec, ExportProvided, ExportSpec, + ExportsInfoArtifact, ExportsInfoGetter, ExportsOfExportsSpec, ExportsProcessing, ExportsSpec, + ExportsType, ExtendedReferencedExport, FactorizeInfo, GetUsedNameParam, ModuleDependency, + ModuleGraph, ModuleGraphCacheArtifact, ModuleIdentifier, Nullable, PrefetchExportsInfoMode, + ReferencedExport, RuntimeSpec, TemplateContext, TemplateReplaceSource, UsageState, UsedName, + collect_referenced_export_items, create_exports_object_referenced, create_no_exports_referenced, + property_access, to_normal_comment, }; use rustc_hash::FxHashSet; use swc_core::atoms::Atom; @@ -64,6 +65,7 @@ impl CommonJsExportRequireDependency { // NOTE: // webpack return checked set but never use it // https://github.com/webpack/webpack/blob/08770761c8c7aa1e6e18b77d3deee8cc9871bd87/lib/dependencies/CommonJsExportRequireDependency.js#L283 + #[allow(dead_code)] fn get_star_reexports( &self, mg: &ModuleGraph, @@ -220,8 +222,8 @@ impl Dependency for CommonJsExportRequireDependency { fn get_exports( &self, mg: &ModuleGraph, - mg_cache: &ModuleGraphCacheArtifact, - exports_info_artifact: &ExportsInfoArtifact, + _mg_cache: &ModuleGraphCacheArtifact, + _exports_info_artifact: &ExportsInfoArtifact, ) -> Option { let ids = self.get_ids(mg); @@ -231,59 +233,110 @@ impl Dependency for CommonJsExportRequireDependency { }; let from = mg.connection_by_dependency_id(&self.id)?; Some(ExportsSpec { - exports: ExportsOfExportsSpec::Names(vec![ExportNameOrSpec::ExportSpec(ExportSpec { - name: name.to_owned(), - from: Some(from.to_owned()), - can_mangle: Some(!OBJECT_PROTOTYPE_METHODS.contains(&name.as_str())), - export: Some(if ids.is_empty() { - Nullable::Null - } else { - Nullable::Value(ids.to_vec()) - }), - ..Default::default() - })]), + exports: ExportsOfExportsSpec::Names(vec![]), + processing: ExportsProcessing::DeferredReexport(vec![DeferredReexportSpec { + target_module: *from.module_identifier(), + dep_id: self.id, + priority: None, + can_mangle: Some( + !OBJECT_PROTOTYPE_METHODS.contains(&name.as_str()) + && !self.is_all_exported_by_module_exports(), + ), + terminal_binding: false, + items: vec![DeferredReexportItem { + exposed_name: name.to_owned(), + target_path: if ids.is_empty() { + Nullable::Null + } else { + Nullable::Value(ids.to_vec()) + }, + hidden: false, + }], + star_exports: None, + }]), dependencies: Some(vec![*from.module_identifier()]), ..Default::default() }) } else if self.names.is_empty() { let from = mg.connection_by_dependency_id(&self.id)?; - if let Some(reexport_info) = self.get_star_reexports( + if ids.is_empty() { + if let Some(reexport_info) = self.get_star_reexports( + mg, + _mg_cache, + _exports_info_artifact, + None, + from.module_identifier(), + ) { + Some(ExportsSpec { + exports: ExportsOfExportsSpec::Names( + reexport_info + .iter() + .map(|name| { + let mut export = ids.to_vec(); + export.extend(vec![name.to_owned()]); + ExportNameOrSpec::ExportSpec(ExportSpec { + name: name.to_owned(), + from: Some(from.to_owned()), + export: Some(Nullable::Value(export)), + can_mangle: Some(!self.is_all_exported_by_module_exports()), + ..Default::default() + }) + }) + .collect_vec(), + ), + dependencies: Some(vec![*from.module_identifier()]), + ..Default::default() + }) + } else { + Some(ExportsSpec { + exports: ExportsOfExportsSpec::UnknownExports, + from: Some(from.to_owned()), + can_mangle: Some(!self.is_all_exported_by_module_exports()), + dependencies: Some(vec![*from.module_identifier()]), + ..Default::default() + }) + } + } else if let Some(reexport_info) = self.get_star_reexports( mg, - mg_cache, - exports_info_artifact, + _mg_cache, + _exports_info_artifact, None, from.module_identifier(), ) { + let deferred_items = reexport_info + .iter() + .map(|name| { + let mut target_path = ids.to_vec(); + target_path.push(name.to_owned()); + DeferredReexportItem { + exposed_name: name.to_owned(), + target_path: Nullable::Value(target_path), + hidden: false, + } + }) + .collect_vec(); + Some(ExportsSpec { - exports: ExportsOfExportsSpec::Names( - reexport_info - .iter() - .map(|name| { - let mut export = ids.to_vec(); - export.extend(vec![name.to_owned()]); - ExportNameOrSpec::ExportSpec(ExportSpec { - name: name.to_owned(), - from: Some(from.to_owned()), - export: Some(Nullable::Value(export)), - // `module.exports = require("./m")` can't be mangled - can_mangle: Some(!self.is_all_exported_by_module_exports()), - ..Default::default() - }) - }) - .collect_vec(), - ), + exports: ExportsOfExportsSpec::Names(vec![]), + processing: if deferred_items.is_empty() { + ExportsProcessing::Immediate + } else { + ExportsProcessing::DeferredReexport(vec![DeferredReexportSpec { + target_module: *from.module_identifier(), + dep_id: self.id, + priority: None, + can_mangle: Some(!self.is_all_exported_by_module_exports()), + terminal_binding: false, + items: deferred_items, + star_exports: None, + }]) + }, dependencies: Some(vec![*from.module_identifier()]), ..Default::default() }) } else { Some(ExportsSpec { exports: ExportsOfExportsSpec::UnknownExports, - from: if ids.is_empty() { - Some(from.to_owned()) - } else { - None - }, - // `module.exports = require("./m")` can't be mangled can_mangle: Some(!self.is_all_exported_by_module_exports()), dependencies: Some(vec![*from.module_identifier()]), ..Default::default() diff --git a/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_exports_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_exports_dependency.rs index 07a852f6ba79..a5450124cc7b 100644 --- a/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_exports_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_exports_dependency.rs @@ -6,9 +6,10 @@ use rspack_core::{ AsContextDependency, AsModuleDependency, Dependency, DependencyCategory, DependencyCodeGeneration, DependencyId, DependencyRange, DependencyTemplate, DependencyTemplateType, DependencyType, ExportNameOrSpec, ExportSpec, ExportsInfoArtifact, - ExportsInfoGetter, ExportsOfExportsSpec, ExportsSpec, GetUsedNameParam, InitFragmentExt, - InitFragmentKey, InitFragmentStage, ModuleGraph, ModuleGraphCacheArtifact, NormalInitFragment, - PrefetchExportsInfoMode, TemplateContext, TemplateReplaceSource, UsedName, property_access, + ExportsInfoGetter, ExportsOfExportsSpec, ExportsSpec, GetExportsCacheability, GetUsedNameParam, + InitFragmentExt, InitFragmentKey, InitFragmentStage, ModuleGraph, ModuleGraphCacheArtifact, + NormalInitFragment, PrefetchExportsInfoMode, TemplateContext, TemplateReplaceSource, UsedName, + property_access, }; use swc_core::atoms::Atom; @@ -123,6 +124,14 @@ impl Dependency for CommonJsExportsDependency { fn could_affect_referencing_module(&self) -> rspack_core::AffectType { rspack_core::AffectType::False } + + fn get_exports_cacheability( + &self, + _mg: &ModuleGraph, + _mg_cache: &ModuleGraphCacheArtifact, + ) -> GetExportsCacheability { + GetExportsCacheability::Static + } } impl AsModuleDependency for CommonJsExportsDependency {} diff --git a/crates/rspack_plugin_javascript/src/dependency/esm/esm_export_expression_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/esm/esm_export_expression_dependency.rs index 074d29023dfc..f615fa3a5564 100644 --- a/crates/rspack_plugin_javascript/src/dependency/esm/esm_export_expression_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/esm/esm_export_expression_dependency.rs @@ -5,9 +5,10 @@ use rspack_core::{ AsContextDependency, AsModuleDependency, DEFAULT_EXPORT, Dependency, DependencyCodeGeneration, DependencyId, DependencyLocation, DependencyRange, DependencyTemplate, DependencyTemplateType, DependencyType, ESMExportInitFragment, ExportNameOrSpec, ExportsInfoArtifact, ExportsInfoGetter, - ExportsOfExportsSpec, ExportsSpec, ForwardId, GetUsedNameParam, ModuleGraph, - ModuleGraphCacheArtifact, PrefetchExportsInfoMode, TemplateContext, TemplateReplaceSource, - UsedName, property_access, rspack_sources::ReplacementEnforce, + ExportsOfExportsSpec, ExportsProcessing, ExportsSpec, ForwardId, GetExportsCacheability, + GetUsedNameParam, ModuleGraph, ModuleGraphCacheArtifact, PrefetchExportsInfoMode, + TemplateContext, TemplateReplaceSource, UsedName, property_access, + rspack_sources::ReplacementEnforce, }; use swc_core::atoms::Atom; @@ -92,6 +93,7 @@ impl Dependency for ESMExportExpressionDependency { exports: ExportsOfExportsSpec::Names(vec![ExportNameOrSpec::String( JS_DEFAULT_KEYWORD.clone(), )]), + processing: ExportsProcessing::Immediate, priority: Some(1), can_mangle: None, terminal_binding: Some(true), @@ -116,6 +118,14 @@ impl Dependency for ESMExportExpressionDependency { rspack_core::AffectType::False } + fn get_exports_cacheability( + &self, + _mg: &ModuleGraph, + _mg_cache: &ModuleGraphCacheArtifact, + ) -> GetExportsCacheability { + GetExportsCacheability::Static + } + fn forward_id(&self) -> ForwardId { ForwardId::Id(JS_DEFAULT_KEYWORD.clone()) } diff --git a/crates/rspack_plugin_javascript/src/dependency/esm/esm_export_imported_specifier_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/esm/esm_export_imported_specifier_dependency.rs index 61fe361c5b7f..eccb0a4b5951 100644 --- a/crates/rspack_plugin_javascript/src/dependency/esm/esm_export_imported_specifier_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/esm/esm_export_imported_specifier_dependency.rs @@ -7,23 +7,24 @@ use rspack_cacheable::{ }; use rspack_collections::{IdentifierMap, IdentifierSet}; use rspack_core::{ - AsContextDependency, ChunkGraph, ConditionalInitFragment, ConnectionState, Dependency, - DependencyCategory, DependencyCodeGeneration, DependencyCondition, DependencyConditionFn, - DependencyId, DependencyLocation, DependencyRange, DependencyTemplate, DependencyTemplateType, - DependencyType, DetermineExportAssignmentsKey, ESMExportInitFragment, ExportMode, - ExportModeDynamicReexport, ExportModeEmptyStar, ExportModeFakeNamespaceObject, - ExportModeNormalReexport, ExportModeReexportDynamicDefault, ExportModeReexportNamedDefault, - ExportModeReexportNamespaceObject, ExportModeReexportUndefined, ExportModeUnused, - ExportNameOrSpec, ExportPresenceMode, ExportProvided, ExportSpec, ExportsInfoArtifact, - ExportsInfoGetter, ExportsOfExportsSpec, ExportsSpec, ExportsType, ExtendedReferencedExport, - FactorizeInfo, ForwardId, GetUsedNameParam, ImportAttributes, ImportPhase, InitFragmentExt, - InitFragmentKey, InitFragmentStage, JavascriptParserOptions, LazyUntil, ModuleDependency, - ModuleGraph, ModuleGraphCacheArtifact, ModuleIdentifier, NormalInitFragment, NormalReexportItem, - PrefetchExportsInfoMode, PrefetchedExportsInfoWrapper, ResourceIdentifier, RuntimeCondition, - RuntimeGlobals, RuntimeSpec, StarReexportsInfo, TemplateContext, TemplateReplaceSource, - UsageState, UsedName, collect_referenced_export_items, create_exports_object_referenced, - create_no_exports_referenced, filter_runtime, get_exports_type, get_runtime_key, - get_terminal_binding, property_access, property_name, + AsContextDependency, ChunkGraph, ConditionalInitFragment, ConnectionState, DeferredReexportItem, + DeferredReexportSpec, Dependency, DependencyCategory, DependencyCodeGeneration, + DependencyCondition, DependencyConditionFn, DependencyId, DependencyLocation, DependencyRange, + DependencyTemplate, DependencyTemplateType, DependencyType, DetermineExportAssignmentsKey, + ESMExportInitFragment, ExportMode, ExportModeDynamicReexport, ExportModeEmptyStar, + ExportModeFakeNamespaceObject, ExportModeNormalReexport, ExportModeReexportDynamicDefault, + ExportModeReexportNamedDefault, ExportModeReexportNamespaceObject, ExportModeReexportUndefined, + ExportModeUnused, ExportNameOrSpec, ExportPresenceMode, ExportProvided, ExportSpec, + ExportsInfoArtifact, ExportsInfoGetter, ExportsOfExportsSpec, ExportsProcessing, ExportsSpec, + ExportsType, ExtendedReferencedExport, FactorizeInfo, ForwardId, GetExportsCacheability, + GetUsedNameParam, ImportAttributes, ImportPhase, InitFragmentExt, InitFragmentKey, + InitFragmentStage, JavascriptParserOptions, LazyUntil, ModuleDependency, ModuleGraph, + ModuleGraphCacheArtifact, ModuleIdentifier, NormalInitFragment, NormalReexportItem, + PrefetchExportsInfoMode, PrefetchedExportsInfoWrapper, ReferencedExport, ResourceIdentifier, + RuntimeCondition, RuntimeGlobals, RuntimeSpec, StarReexportsInfo, TemplateContext, + TemplateReplaceSource, UsageState, UsedName, collect_referenced_export_items, + create_exports_object_referenced, create_no_exports_referenced, filter_runtime, get_exports_type, + get_runtime_key, get_terminal_binding, property_access, property_name, render_make_deferred_namespace_mode_from_exports_type, to_normal_comment, }; use rspack_error::{Diagnostic, Error, Severity}; @@ -248,6 +249,8 @@ impl ESMExportImportedSpecifierDependency { checked: false, export_info, }], + is_star_reexport: false, + ignored_exports: Default::default(), }), } }; @@ -283,7 +286,10 @@ impl ESMExportImportedSpecifierDependency { }; if exports.is_empty() { - return ExportMode::EmptyStar(ExportModeEmptyStar { hidden }); + return ExportMode::EmptyStar(ExportModeEmptyStar { + ignored_exports, + hidden, + }); } let mut items = exports @@ -312,7 +318,11 @@ impl ESMExportImportedSpecifierDependency { } } - ExportMode::NormalReexport(ExportModeNormalReexport { items }) + ExportMode::NormalReexport(ExportModeNormalReexport { + items, + is_star_reexport: true, + ignored_exports, + }) } pub fn get_star_reexports( @@ -1190,16 +1200,25 @@ impl Dependency for ESMExportImportedSpecifierDependency { // https://github.com/webpack/webpack/blob/ac7e531436b0d47cd88451f497cdfd0dad41535d/lib/dependencies/HarmonyExportImportedSpecifierDependency.js#L630-L742 unreachable!() } - ExportMode::EmptyStar(mode) => Some(ExportsSpec { - exports: ExportsOfExportsSpec::Names(vec![]), - hide_export: mode.hidden, - dependencies: Some(vec![ - *mg - .module_identifier_by_dependency_id(self.id()) - .expect("should have module"), - ]), - ..Default::default() - }), + ExportMode::EmptyStar(mode) => { + let from = mg + .connection_by_dependency_id(self.id()) + .expect("should have module"); + let mut deferred = DeferredReexportSpec::new_star( + *from.module_identifier(), + self.id, + Vec::new(), + mode.ignored_exports, + mode.hidden.unwrap_or_default(), + ); + deferred.priority = Some(1); + Some(ExportsSpec { + exports: ExportsOfExportsSpec::Names(vec![]), + processing: ExportsProcessing::DeferredReexport(vec![deferred]), + dependencies: Some(vec![*from.module_identifier()]), + ..Default::default() + }) + } ExportMode::ReexportDynamicDefault(mode) => { let from = mg.connection_by_dependency_id(self.id()); Some(ExportsSpec { @@ -1251,8 +1270,6 @@ impl Dependency for ESMExportImportedSpecifierDependency { exports: Some(vec![ExportNameOrSpec::ExportSpec(ExportSpec { name: "default".into(), can_mangle: Some(false), - from: from.cloned(), - export: Some(rspack_core::Nullable::Null), ..Default::default() })]), from: from.cloned(), @@ -1273,25 +1290,74 @@ impl Dependency for ESMExportImportedSpecifierDependency { ..Default::default() }), ExportMode::NormalReexport(mode) => { - let from = mg.connection_by_dependency_id(self.id()); + let from = mg + .connection_by_dependency_id(self.id()) + .expect("should have module"); + let target_module = *from.module_identifier(); + if mode.is_star_reexport { + let hidden_exports = mode + .items + .iter() + .filter(|item| item.hidden) + .map(|item| item.name.clone()) + .collect(); + let mut deferred = DeferredReexportSpec::new_star( + target_module, + self.id, + Vec::new(), + mode.ignored_exports, + hidden_exports, + ); + deferred.priority = Some(1); + deferred.can_mangle = None; + deferred.terminal_binding = false; + return Some(ExportsSpec { + priority: Some(1), + exports: ExportsOfExportsSpec::Names(vec![]), + processing: ExportsProcessing::DeferredReexport(vec![deferred]), + dependencies: Some(vec![target_module]), + ..Default::default() + }); + } + + let mut local_exports = Vec::new(); + let mut deferred_items = Vec::new(); + + for item in mode.items { + if item.ids.len() > 1 { + local_exports.push(ExportNameOrSpec::ExportSpec(ExportSpec { + name: item.name, + from: Some(from.clone()), + export: Some(rspack_core::Nullable::Value(item.ids)), + hidden: Some(item.hidden), + ..Default::default() + })); + } else { + deferred_items.push(DeferredReexportItem { + exposed_name: item.name, + target_path: rspack_core::Nullable::Value(item.ids), + hidden: item.hidden, + }); + } + } + Some(ExportsSpec { priority: Some(1), - exports: ExportsOfExportsSpec::Names( - mode - .items - .into_iter() - .map(|item| { - ExportNameOrSpec::ExportSpec(ExportSpec { - name: item.name, - from: from.cloned(), - export: Some(rspack_core::Nullable::Value(item.ids)), - hidden: Some(item.hidden), - ..Default::default() - }) - }) - .collect::>(), - ), - dependencies: Some(vec![*from.expect("should have module").module_identifier()]), + exports: ExportsOfExportsSpec::Names(local_exports), + processing: if deferred_items.is_empty() { + ExportsProcessing::Immediate + } else { + ExportsProcessing::DeferredReexport(vec![DeferredReexportSpec { + target_module, + dep_id: self.id, + priority: Some(1), + can_mangle: None, + terminal_binding: false, + items: deferred_items, + star_exports: None, + }]) + }, + dependencies: Some(vec![target_module]), ..Default::default() }) } @@ -1334,6 +1400,18 @@ impl Dependency for ESMExportImportedSpecifierDependency { Some(self.source_order) } + fn get_exports_cacheability( + &self, + _mg: &ModuleGraph, + _mg_cache: &ModuleGraphCacheArtifact, + ) -> GetExportsCacheability { + if self.name.is_some() && !self.lazy_make { + GetExportsCacheability::Static + } else { + GetExportsCacheability::Dynamic + } + } + // #[tracing::instrument(skip_all)] fn get_diagnostics( &self, @@ -1407,10 +1485,6 @@ impl Dependency for ESMExportImportedSpecifierDependency { | ExportMode::ReexportNamespaceObject(ExportModeReexportNamespaceObject { ref partial_namespace_export_info, .. - }) - | ExportMode::ReexportFakeNamespaceObject(ExportModeFakeNamespaceObject { - ref partial_namespace_export_info, - .. }) => { let mut referenced_exports = vec![]; collect_referenced_export_items( @@ -1427,6 +1501,32 @@ impl Dependency for ESMExportImportedSpecifierDependency { .map(|i| ExtendedReferencedExport::Array(i.into_iter().map(|i| i.to_owned()).collect())) .collect::>() } + ExportMode::ReexportFakeNamespaceObject(ExportModeFakeNamespaceObject { + ref partial_namespace_export_info, + .. + }) => { + let mut referenced_exports = vec![]; + collect_referenced_export_items( + exports_info_artifact, + runtime, + &mut referenced_exports, + vec![], + Some(partial_namespace_export_info.as_data(exports_info_artifact)), + true, + &mut Default::default(), + ); + referenced_exports + .into_iter() + .map(|name| { + ExtendedReferencedExport::Export(ReferencedExport { + name: name.into_iter().map(|item| item.to_owned()).collect(), + can_mangle: false, + can_inline: false, + ns_access: false, + }) + }) + .collect::>() + } ExportMode::NormalReexport(mode) => { let mut referenced_exports = vec![]; for item in &mode.items { diff --git a/crates/rspack_plugin_javascript/src/dependency/esm/esm_export_specifier_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/esm/esm_export_specifier_dependency.rs index a28088c7685e..e6480c794e9a 100644 --- a/crates/rspack_plugin_javascript/src/dependency/esm/esm_export_specifier_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/esm/esm_export_specifier_dependency.rs @@ -5,8 +5,9 @@ use rspack_core::{ DependencyCodeGeneration, DependencyId, DependencyLocation, DependencyRange, DependencyTemplate, DependencyTemplateType, DependencyType, ESMExportInitFragment, EvaluatedInlinableValue, ExportNameOrSpec, ExportSpec, ExportsInfoArtifact, ExportsInfoGetter, ExportsOfExportsSpec, - ExportsSpec, GetUsedNameParam, LazyUntil, ModuleGraph, ModuleGraphCacheArtifact, - PrefetchExportsInfoMode, TSEnumValue, TemplateContext, TemplateReplaceSource, UsedName, + ExportsProcessing, ExportsSpec, GetExportsCacheability, GetUsedNameParam, LazyUntil, ModuleGraph, + ModuleGraphCacheArtifact, PrefetchExportsInfoMode, TSEnumValue, TemplateContext, + TemplateReplaceSource, UsedName, }; use swc_core::ecma::atoms::Atom; @@ -91,6 +92,7 @@ impl Dependency for ESMExportSpecifierDependency { }), ..Default::default() })]), + processing: ExportsProcessing::Immediate, priority: Some(1), can_mangle: None, terminal_binding: Some(true), @@ -115,6 +117,14 @@ impl Dependency for ESMExportSpecifierDependency { rspack_core::AffectType::False } + fn get_exports_cacheability( + &self, + _mg: &ModuleGraph, + _mg_cache: &ModuleGraphCacheArtifact, + ) -> GetExportsCacheability { + GetExportsCacheability::Static + } + fn lazy(&self) -> Option { Some(LazyUntil::Local(self.name.clone())) } diff --git a/crates/rspack_plugin_javascript/src/plugin/flag_dependency_exports_plugin.rs b/crates/rspack_plugin_javascript/src/plugin/flag_dependency_exports_plugin.rs index 9b29051555bb..0225c026b219 100644 --- a/crates/rspack_plugin_javascript/src/plugin/flag_dependency_exports_plugin.rs +++ b/crates/rspack_plugin_javascript/src/plugin/flag_dependency_exports_plugin.rs @@ -1,181 +1,94 @@ -use rayon::prelude::*; -use rspack_collections::{IdentifierMap, IdentifierSet}; +mod collector; +mod local_apply; +mod propagation; +mod types; + +use rspack_collections::IdentifierSet; use rspack_core::{ AsyncModulesArtifact, BuildMetaExportsType, Compilation, CompilationFinishModules, - DependenciesBlock, DependencyId, EvaluatedInlinableValue, ExportInfo, ExportInfoData, - ExportNameOrSpec, ExportProvided, ExportsInfo, ExportsInfoArtifact, ExportsInfoData, - ExportsOfExportsSpec, ExportsSpec, GetTargetResult, Logger, ModuleGraph, - ModuleGraphCacheArtifact, ModuleGraphConnection, ModuleIdentifier, Nullable, Plugin, - PrefetchExportsInfoMode, get_target, - incremental::{self, IncrementalPasses}, + DependencyExportsAnalysisArtifact, DependencyId, EvaluatedInlinableValue, ExportInfo, + ExportInfoData, ExportNameOrSpec, ExportProvided, ExportsInfo, ExportsInfoArtifact, + ExportsInfoData, ExportsInfoRead, ExportsOfExportsSpec, ExportsSpec, GetTargetResult, Logger, + ModuleGraph, ModuleGraphConnection, ModuleIdentifier, Nullable, Plugin, PrefetchExportsInfoMode, + get_target, + incremental::{self, IncrementalPasses, Mutation}, }; use rspack_error::Result; use rspack_hook::{plugin, plugin_hook}; -use rspack_util::fx_hash::{FxIndexMap, FxIndexSet}; use swc_core::ecma::atoms::Atom; -struct FlagDependencyExportsState<'a> { - mg: &'a ModuleGraph, - mg_cache: &'a ModuleGraphCacheArtifact, - exports_info_artifact: &'a mut ExportsInfoArtifact, -} - -impl<'a> FlagDependencyExportsState<'a> { - pub fn new( - mg: &'a ModuleGraph, - mg_cache: &'a ModuleGraphCacheArtifact, - exports_info_artifact: &'a mut ExportsInfoArtifact, - ) -> Self { - Self { - mg, - mg_cache, - exports_info_artifact, - } +fn collect_affected_modules(compilation: &Compilation) -> IdentifierSet { + let module_graph = compilation.get_module_graph(); + if let Some(mutations) = compilation + .incremental + .mutations_read(IncrementalPasses::FINISH_MODULES) + { + let modules = mutations.get_affected_modules_with_module_graph(module_graph); + tracing::debug!(target: incremental::TRACING_TARGET, passes = %IncrementalPasses::FINISH_MODULES, %mutations, ?modules); + let logger = compilation.get_logger("rspack.incremental.finishModules"); + logger.log(format!( + "{} modules are affected, {} in total", + modules.len(), + module_graph.modules_len() + )); + modules + } else { + module_graph.modules_keys().copied().collect() } +} - pub fn apply(&mut self, modules: IdentifierSet) { - // initialize the exports info data and their provided info for all modules - for module_id in &modules { - let exports_type_unset = self - .mg - .module_by_identifier(module_id) - .expect("should have module") - .build_meta() - .exports_type - == BuildMetaExportsType::Unset; - let exports_info = self - .exports_info_artifact - .get_exports_info_data_mut(module_id); - // Reset exports provide info back to initial - exports_info.reset_provide_info(); - if exports_type_unset - && !matches!( - exports_info.other_exports_info().provided(), - Some(ExportProvided::Unknown) - ) - { - exports_info.set_has_provide_info(); - exports_info.set_unknown_exports_provided(false, None, None, None, None); - continue; - } +fn collect_staged_modules( + dependency_exports_analysis_artifact: &DependencyExportsAnalysisArtifact, + affected_modules: &IdentifierSet, +) -> IdentifierSet { + let mut staged_modules = affected_modules.clone(); + staged_modules.extend(dependency_exports_analysis_artifact.dirty_modules()); + staged_modules +} +fn prepare_provide_info( + module_graph: &ModuleGraph, + exports_info_artifact: &mut ExportsInfoArtifact, + affected_modules: &IdentifierSet, +) { + for module_identifier in affected_modules { + let exports_type_unset = module_graph + .module_by_identifier(module_identifier) + .expect("should have module") + .build_meta() + .exports_type + == BuildMetaExportsType::Unset; + let exports_info = exports_info_artifact.get_exports_info_data_mut(module_identifier); + exports_info.reset_provide_info(); + if exports_type_unset + && !matches!( + exports_info.other_exports_info().provided(), + Some(ExportProvided::Unknown) + ) + { exports_info.set_has_provide_info(); + exports_info.set_unknown_exports_provided(false, None, None, None, None); + continue; } - // collect the exports specs from all modules and their dependencies - // and then merge the exports specs to exports info data - // and collect the dependencies which will be used to backtrack when target exports info is changed - let mut batch = modules; - let mut dependencies: IdentifierMap = - IdentifierMap::with_capacity_and_hasher(batch.len(), Default::default()); - while !batch.is_empty() { - let modules = std::mem::take(&mut batch); - - // collect the exports specs from modules by calling `dependency.get_exports` - let module_exports_specs = modules - .into_par_iter() - .map(|module_id| { - let exports_specs = collect_module_exports_specs( - &module_id, - self.mg, - self.mg_cache, - self.exports_info_artifact, - ) - .unwrap_or_default(); - (module_id, exports_specs) - }) - .collect::>(); - - let mut changed_modules = - IdentifierSet::with_capacity_and_hasher(module_exports_specs.len(), Default::default()); - - // partition the exports specs into two parts: - // 1. if the exports info data do not have `redirect_to` and exports specs do not have nested `exports`, - // then the merging only affect the exports info data itself and can be done parallelly - // 2. if the exports info data have `redirect_to` or exports specs have nested `exports`, - // then the merging will affect the redirected exports info data or create a new exports info data - // and this merging can not be done parallelly - // - // There are two cases that the `redirect_to` or nested `exports` exist: - // 1. exports from json dependency which has nested json object data - // 2. exports from an esm reexport and the target is a commonjs module which should create a interop `default` export - let (non_nested_specs, has_nested_specs): (Vec<_>, Vec<_>) = module_exports_specs - .into_iter() - .partition(|(_mid, (_, has_nested_exports))| { - if *has_nested_exports { - return false; - } - true - }); - - // parallelize the merging of exports specs to exports info data - let non_nested_tasks = non_nested_specs - .into_par_iter() - .map(|(module_id, (exports_specs, _))| { - let mut changed = false; - let mut exports_info = self - .exports_info_artifact - .get_exports_info_data(&module_id) - .clone(); - let mut dependencies = Vec::with_capacity(exports_specs.len()); - for (dep_id, exports_spec) in exports_specs { - let (is_changed, changed_dependencies) = process_exports_spec_without_nested( - self.mg, - self.exports_info_artifact, - &module_id, - dep_id, - &exports_spec, - &mut exports_info, - ); - changed |= is_changed; - dependencies.extend(changed_dependencies); - } - (module_id, changed, dependencies, exports_info) - }) - .collect::>(); - - // handle collected side effects and apply the merged exports info data to module graph - for (module_id, changed, changed_dependencies, exports_info) in non_nested_tasks { - if changed { - changed_modules.insert(module_id); - } - for (module_id, dep_id) in changed_dependencies { - dependencies.entry(module_id).or_default().insert(dep_id); - } - self - .exports_info_artifact - .set_exports_info_by_id(exports_info.id(), exports_info); - } + exports_info.set_has_provide_info(); + } +} - // serializing the merging of exports specs to nested exports info data - for (module_id, (exports_specs, _)) in has_nested_specs { - let mut changed = false; - for (dep_id, exports_spec) in exports_specs { - let (is_changed, changed_dependencies) = process_exports_spec( - self.mg, - self.exports_info_artifact, - &module_id, - dep_id, - &exports_spec, - ); - changed |= is_changed; - for (module_id, dep_id) in changed_dependencies { - dependencies.entry(module_id).or_default().insert(dep_id); - } - } - if changed { - changed_modules.insert(module_id); - } - } +fn prune_removed_modules( + compilation: &Compilation, + dependency_exports_analysis_artifact: &mut DependencyExportsAnalysisArtifact, +) { + let Some(mutations) = compilation + .incremental + .mutations_read(IncrementalPasses::FINISH_MODULES) + else { + return; + }; - // collect the dependencies which will be used to backtrack when target exports info is changed - batch.extend(changed_modules.into_iter().flat_map(|m| { - dependencies - .get(&m) - .into_iter() - .flat_map(|d| d.iter()) - .copied() - })); + for mutation in mutations.iter() { + if let Mutation::ModuleRemove { module } = mutation { + dependency_exports_analysis_artifact.remove_module(module); } } } @@ -199,28 +112,28 @@ async fn finish_modules( compilation: &Compilation, _async_modules_artifact: &mut AsyncModulesArtifact, exports_info_artifact: &mut ExportsInfoArtifact, + dependency_exports_analysis_artifact: &mut DependencyExportsAnalysisArtifact, ) -> Result<()> { let module_graph = compilation.get_module_graph(); - let modules: IdentifierSet = if let Some(mutations) = compilation - .incremental - .mutations_read(IncrementalPasses::FINISH_MODULES) - { - let modules = mutations.get_affected_modules_with_module_graph(module_graph); - tracing::debug!(target: incremental::TRACING_TARGET, passes = %IncrementalPasses::FINISH_MODULES, %mutations, ?modules); - let logger = compilation.get_logger("rspack.incremental.finishModules"); - logger.log(format!( - "{} modules are affected, {} in total", - modules.len(), - module_graph.modules_len() - )); - modules - } else { - module_graph.modules_keys().copied().collect() - }; + let affected_modules = collect_affected_modules(compilation); let module_graph_cache = compilation.module_graph_cache_artifact.clone(); - - FlagDependencyExportsState::new(module_graph, &module_graph_cache, exports_info_artifact) - .apply(modules); + prune_removed_modules(compilation, dependency_exports_analysis_artifact); + let staged_modules = + collect_staged_modules(dependency_exports_analysis_artifact, &affected_modules); + prepare_provide_info(module_graph, exports_info_artifact, &staged_modules); + local_apply::apply_local_exports( + module_graph, + &module_graph_cache, + exports_info_artifact, + dependency_exports_analysis_artifact, + &staged_modules, + )?; + propagation::propagate_deferred_reexports( + module_graph, + &module_graph_cache, + exports_info_artifact, + dependency_exports_analysis_artifact, + )?; Ok(()) } @@ -239,54 +152,10 @@ impl Plugin for FlagDependencyExportsPlugin { } } -/** - * Collect all exports specs from a module and its dependencies - * by calling `dependency.get_exports` for each dependency. - */ -fn collect_module_exports_specs( - module_id: &ModuleIdentifier, - mg: &ModuleGraph, - mg_cache: &ModuleGraphCacheArtifact, - exports_info_artifact: &ExportsInfoArtifact, -) -> Option<(FxIndexMap, bool)> { - let mut has_nested_exports = false; - fn walk_block( - block: &B, - dep_ids: &mut FxIndexSet, - mg: &ModuleGraph, - ) { - dep_ids.extend(block.get_dependencies().iter().copied()); - for block_id in block.get_blocks() { - if let Some(block) = mg.block_by_id(block_id) { - walk_block(block, dep_ids, mg); - } - } - } - - let block = mg.module_by_identifier(module_id)?.as_ref(); - let mut dep_ids = FxIndexSet::default(); - walk_block(block, &mut dep_ids, mg); - - // There is no need to use the cache here - // because the `get_exports` of each dependency will only be called once - // mg_cache.freeze(); - let res = dep_ids - .into_iter() - .filter_map(|id| { - let dep = mg.dependency_by_id(&id); - let exports_spec = dep.get_exports(mg, mg_cache, exports_info_artifact)?; - has_nested_exports |= exports_spec.has_nested_exports(); - Some((id, exports_spec)) - }) - .collect::>(); - // mg_cache.unfreeze(); - Some((res, has_nested_exports)) -} - /// Merge exports specs to exports info data /// and also collect the dependencies /// which will be used to backtrack when target exports info is changed -pub fn process_exports_spec( +pub(super) fn process_exports_spec( mg: &ModuleGraph, exports_info_artifact: &mut ExportsInfoArtifact, module_id: &ModuleIdentifier, @@ -360,9 +229,46 @@ pub fn process_exports_spec( /// which will be used to backtrack when target exports info is changed /// This method is used for the case that the exports info data will not be nested modified /// that means this exports info can be modified parallelly -fn process_exports_spec_without_nested( +pub(super) fn process_exports_spec_without_nested( mg: &ModuleGraph, - exports_info_artifact: &ExportsInfoArtifact, + exports_info_artifact: &T, + module_id: &ModuleIdentifier, + dep_id: DependencyId, + export_desc: &ExportsSpec, + exports_info: &mut ExportsInfoData, +) -> (bool, Vec<(ModuleIdentifier, ModuleIdentifier)>) { + process_exports_spec_without_nested_impl::( + mg, + exports_info_artifact, + module_id, + dep_id, + export_desc, + exports_info, + ) +} + +pub(super) fn process_exports_spec_without_nested_no_deps( + mg: &ModuleGraph, + exports_info_artifact: &T, + module_id: &ModuleIdentifier, + dep_id: DependencyId, + export_desc: &ExportsSpec, + exports_info: &mut ExportsInfoData, +) -> bool { + process_exports_spec_without_nested_impl::( + mg, + exports_info_artifact, + module_id, + dep_id, + export_desc, + exports_info, + ) + .0 +} + +fn process_exports_spec_without_nested_impl( + mg: &ModuleGraph, + exports_info_artifact: &T, module_id: &ModuleIdentifier, dep_id: DependencyId, export_desc: &ExportsSpec, @@ -396,7 +302,7 @@ fn process_exports_spec_without_nested( } ExportsOfExportsSpec::NoExports => {} ExportsOfExportsSpec::Names(ele) => { - let (merge_changed, merge_dependencies) = merge_exports_without_nested( + let (merge_changed, merge_dependencies) = merge_exports_without_nested::( mg, exports_info_artifact, module_id, @@ -411,11 +317,13 @@ fn process_exports_spec_without_nested( dep_id, ); changed |= merge_changed; - dependencies.extend(merge_dependencies); + if COLLECT_DEPS { + dependencies.extend(merge_dependencies); + } } } - if let Some(export_dependencies) = export_dependencies { + if COLLECT_DEPS && let Some(export_dependencies) = export_dependencies { for export_dep in export_dependencies { dependencies.push((*export_dep, *module_id)); } @@ -474,9 +382,9 @@ impl<'a> ParsedExportSpec<'a> { /// /// This method is used for the case that the exports info data will not be nested modified /// that means this exports info can be modified parallelly -fn merge_exports_without_nested( +fn merge_exports_without_nested( mg: &ModuleGraph, - exports_info_artifact: &ExportsInfoArtifact, + exports_info_artifact: &T, module_id: &ModuleIdentifier, exports_info: &mut ExportsInfoData, exports: &[ExportNameOrSpec], @@ -513,7 +421,7 @@ fn merge_exports_without_nested( let (target_exports_info, target_module) = find_target_exports_info(mg, exports_info_artifact, export_info); - if let Some(target_module) = target_module { + if COLLECT_DEPS && let Some(target_module) = target_module { dependencies.push((target_module, *module_id)); } @@ -724,9 +632,9 @@ fn set_export_target( changed } -fn find_target_exports_info( +fn find_target_exports_info( mg: &ModuleGraph, - exports_info_artifact: &ExportsInfoArtifact, + exports_info_artifact: &T, export_info: &ExportInfoData, ) -> (Option, Option) { // Recalculate target exportsInfo diff --git a/crates/rspack_plugin_javascript/src/plugin/flag_dependency_exports_plugin/collector.rs b/crates/rspack_plugin_javascript/src/plugin/flag_dependency_exports_plugin/collector.rs new file mode 100644 index 000000000000..fe01c943e190 --- /dev/null +++ b/crates/rspack_plugin_javascript/src/plugin/flag_dependency_exports_plugin/collector.rs @@ -0,0 +1,280 @@ +use std::sync::Arc; + +use rayon::prelude::*; +use rspack_collections::IdentifierSet; +use rspack_core::{ + DependenciesBlock, DependencyExportsAnalysisArtifact, DependencyId, ExportsInfoArtifact, + ExportsProcessing, ExportsSpec, GetExportsCacheability, ModuleDependencyExportsAnalysis, + ModuleGraph, ModuleGraphCacheArtifact, ModuleIdentifier, +}; +use rspack_error::Result; +use rspack_util::fx_hash::FxIndexSet; + +use super::types::NormalizedModuleAnalysis; + +pub(super) fn normalize_exports_spec(mut spec: ExportsSpec) -> NormalizedModuleAnalysis { + match std::mem::take(&mut spec.processing) { + ExportsProcessing::Immediate => NormalizedModuleAnalysis::from_local(spec), + ExportsProcessing::DeferredReexport(deferred_reexports) => { + NormalizedModuleAnalysis::from_deferred(spec, deferred_reexports) + } + } +} + +pub(super) fn collect_module_analysis( + module_graph: &ModuleGraph, + module_graph_cache: &ModuleGraphCacheArtifact, + exports_info_artifact: &ExportsInfoArtifact, + dependency_exports_analysis_artifact: &mut DependencyExportsAnalysisArtifact, + affected_modules: &IdentifierSet, +) -> Result<()> { + collect_module_analysis_inner( + module_graph, + module_graph_cache, + exports_info_artifact, + dependency_exports_analysis_artifact, + affected_modules, + false, + ) +} + +pub(super) fn collect_module_analysis_with_reuse( + module_graph: &ModuleGraph, + module_graph_cache: &ModuleGraphCacheArtifact, + exports_info_artifact: &ExportsInfoArtifact, + dependency_exports_analysis_artifact: &mut DependencyExportsAnalysisArtifact, + affected_modules: &IdentifierSet, +) -> Result<()> { + collect_module_analysis_inner( + module_graph, + module_graph_cache, + exports_info_artifact, + dependency_exports_analysis_artifact, + affected_modules, + true, + ) +} + +fn collect_module_analysis_inner( + module_graph: &ModuleGraph, + module_graph_cache: &ModuleGraphCacheArtifact, + exports_info_artifact: &ExportsInfoArtifact, + dependency_exports_analysis_artifact: &mut DependencyExportsAnalysisArtifact, + affected_modules: &IdentifierSet, + allow_reuse: bool, +) -> Result<()> { + let dependency_exports_analysis_artifact_ref = &*dependency_exports_analysis_artifact; + let reusable_modules = affected_modules + .iter() + .filter_map(|module_identifier| { + dependency_exports_analysis_artifact + .module(module_identifier) + .filter(|analysis| { + !analysis.dirty() + && ((allow_reuse && analysis.can_reuse_without_recollect()) + || analysis.can_reuse_collected_exports()) + }) + .map(|_| *module_identifier) + }) + .collect::(); + let module_analyses = affected_modules + .par_iter() + .filter_map(|module_identifier| { + if reusable_modules.contains(module_identifier) { + return None; + } + let analysis = collect_module_exports_specs( + module_identifier, + module_graph, + module_graph_cache, + exports_info_artifact, + dependency_exports_analysis_artifact_ref + .module(module_identifier) + .filter(|analysis| !analysis.dirty()) + .map(|analysis| Arc::clone(analysis.dependency_ids_arc())), + ); + Some((*module_identifier, analysis)) + }) + .collect::>(); + + for module_identifier in &reusable_modules { + if let Some(module_analysis) = + dependency_exports_analysis_artifact.module_mut(module_identifier) + { + module_analysis.set_dirty(true); + } + } + + for (module_identifier, mut analysis) in module_analyses { + analysis.flat_dependency_targets.sort_unstable(); + analysis.flat_dependency_targets.dedup(); + analysis.targets.sort_unstable(); + analysis.targets.dedup(); + let mut module_analysis = ModuleDependencyExportsAnalysis::with_staged_analysis( + analysis.dependency_ids, + analysis.fully_static, + analysis.targets, + analysis.flat_dependency_targets, + analysis.flat_local_apply, + analysis.structured_local_apply, + analysis.deferred_reexports, + ); + module_analysis.set_dirty(true); + dependency_exports_analysis_artifact.upsert_module(module_identifier, module_analysis); + } + + Ok(()) +} + +fn collect_module_exports_specs( + module_id: &ModuleIdentifier, + module_graph: &ModuleGraph, + module_graph_cache: &ModuleGraphCacheArtifact, + exports_info_artifact: &ExportsInfoArtifact, + cached_dependency_ids: Option>, +) -> ModuleCollectedAnalysis { + fn walk_block( + block: &B, + dep_ids: &mut FxIndexSet, + module_graph: &ModuleGraph, + ) { + dep_ids.extend(block.get_dependencies().iter().copied()); + for block_id in block.get_blocks() { + if let Some(block) = module_graph.block_by_id(block_id) { + walk_block(block, dep_ids, module_graph); + } + } + } + + let Some(block) = module_graph + .module_by_identifier(module_id) + .map(AsRef::as_ref) + else { + return ModuleCollectedAnalysis::default(); + }; + + let dependency_ids = cached_dependency_ids.unwrap_or_else(|| { + let mut dep_ids = FxIndexSet::default(); + walk_block(block, &mut dep_ids, module_graph); + dep_ids.into_iter().collect::>().into() + }); + + let mut analysis = ModuleCollectedAnalysis { + dependency_ids: Arc::clone(&dependency_ids), + fully_static: true, + ..Default::default() + }; + for dep_id in dependency_ids.iter().copied() { + let dep = module_graph.dependency_by_id(&dep_id); + analysis.fully_static &= matches!( + dep.get_exports_cacheability(module_graph, module_graph_cache), + GetExportsCacheability::Static + ); + let Some(exports_spec) = + dep.get_exports(module_graph, module_graph_cache, exports_info_artifact) + else { + continue; + }; + let normalized = normalize_exports_spec(exports_spec); + let NormalizedModuleAnalysis { + local_apply, + deferred_reexports, + } = normalized; + for (bound_dep_id, exports_spec) in + NormalizedModuleAnalysis::bind_local_apply_with_dep_id(dep_id, local_apply) + { + if exports_spec.has_nested_exports() { + analysis + .structured_local_apply + .push((bound_dep_id, exports_spec)); + } else { + if let Some(dependencies) = exports_spec.dependencies.as_ref() { + analysis + .flat_dependency_targets + .extend(dependencies.iter().copied()); + analysis.targets.extend(dependencies.iter().copied()); + } + analysis.flat_local_apply.push((bound_dep_id, exports_spec)); + } + } + analysis.targets.extend( + deferred_reexports + .iter() + .map(|reexport| reexport.target_module), + ); + analysis.deferred_reexports.extend(deferred_reexports); + } + + analysis +} + +#[derive(Debug, Default)] +struct ModuleCollectedAnalysis { + dependency_ids: Arc<[DependencyId]>, + fully_static: bool, + targets: Vec, + flat_dependency_targets: Vec, + flat_local_apply: Vec<(DependencyId, ExportsSpec)>, + structured_local_apply: Vec<(DependencyId, ExportsSpec)>, + deferred_reexports: Vec, +} + +#[cfg(test)] +mod tests { + use rspack_core::{ + DeferredReexportItem, DeferredReexportSpec, DependencyId, ExportsOfExportsSpec, + ExportsProcessing, ExportsSpec, ModuleIdentifier, Nullable, + }; + + use super::*; + use crate::plugin::flag_dependency_exports_plugin::types::NormalizedModuleAnalysis; + + #[test] + fn normalize_exports_spec_keeps_deferred_reexports_out_of_local_apply() { + let target = ModuleIdentifier::from("leaf"); + let spec = ExportsSpec { + exports: ExportsOfExportsSpec::Names(vec![]), + processing: ExportsProcessing::DeferredReexport(vec![DeferredReexportSpec::new( + target, + DependencyId::from(7), + vec![DeferredReexportItem { + exposed_name: "value".into(), + target_path: Nullable::Value(vec!["value".into()]), + hidden: false, + }], + )]), + ..Default::default() + }; + + let normalized = normalize_exports_spec(spec); + assert!(normalized.local_apply.is_empty()); + assert_eq!(normalized.deferred_reexports.len(), 1); + } + + #[test] + fn bind_local_apply_preserves_fragment_multiplicity_for_one_dependency() { + let dep_id = DependencyId::from(9); + let analysis = NormalizedModuleAnalysis { + local_apply: vec![ + ExportsSpec { + exports: ExportsOfExportsSpec::UnknownExports, + ..Default::default() + }, + ExportsSpec { + hide_export: Some(["value".into()].into_iter().collect()), + ..Default::default() + }, + ], + deferred_reexports: Vec::new(), + }; + + let bound = + NormalizedModuleAnalysis::bind_local_apply_with_dep_id(dep_id, analysis.local_apply); + assert_eq!(bound.len(), 2); + assert!( + bound + .iter() + .all(|(bound_dep_id, _)| *bound_dep_id == dep_id) + ); + } +} diff --git a/crates/rspack_plugin_javascript/src/plugin/flag_dependency_exports_plugin/local_apply.rs b/crates/rspack_plugin_javascript/src/plugin/flag_dependency_exports_plugin/local_apply.rs new file mode 100644 index 000000000000..a0b820e83853 --- /dev/null +++ b/crates/rspack_plugin_javascript/src/plugin/flag_dependency_exports_plugin/local_apply.rs @@ -0,0 +1,472 @@ +use rayon::prelude::*; +use rspack_collections::{IdentifierMap, IdentifierSet}; +use rspack_core::{ + DependencyExportsAnalysisArtifact, ExportsInfoArtifact, ModuleGraph, ModuleGraphCacheArtifact, +}; +use rspack_error::Result; + +use super::{ + collector, process_exports_spec, process_exports_spec_without_nested, + process_exports_spec_without_nested_no_deps, +}; + +pub(super) fn apply_local_exports( + module_graph: &ModuleGraph, + module_graph_cache: &ModuleGraphCacheArtifact, + exports_info_artifact: &mut ExportsInfoArtifact, + dependency_exports_analysis_artifact: &mut DependencyExportsAnalysisArtifact, + initial_modules: &IdentifierSet, +) -> Result<()> { + let mut driver = ArtifactLocalApplyDriver { + module_graph, + module_graph_cache, + dependency_exports_analysis_artifact, + }; + + apply_local_exports_with_driver(&mut driver, exports_info_artifact, initial_modules) +} + +pub(super) fn apply_local_exports_once( + module_graph: &ModuleGraph, + exports_info_artifact: &mut ExportsInfoArtifact, + dependency_exports_analysis_artifact: &DependencyExportsAnalysisArtifact, + modules: &IdentifierSet, +) -> Result { + let mut changed_modules = + IdentifierSet::with_capacity_and_hasher(modules.len(), Default::default()); + apply_flat_local_exports_in_parallel_no_deps( + module_graph, + exports_info_artifact, + dependency_exports_analysis_artifact, + modules, + &mut changed_modules, + )?; + + Ok(changed_modules) +} + +trait LocalApplyDriver { + fn recollect( + &mut self, + exports_info_artifact: &ExportsInfoArtifact, + modules: &IdentifierSet, + ) -> Result<()>; + + fn apply_flat( + &mut self, + exports_info_artifact: &mut ExportsInfoArtifact, + modules: &IdentifierSet, + changed_modules: &mut IdentifierSet, + dependencies: &mut IdentifierMap, + ) -> Result<()>; + + fn apply_structured( + &mut self, + exports_info_artifact: &mut ExportsInfoArtifact, + modules: &IdentifierSet, + changed_modules: &mut IdentifierSet, + dependencies: &mut IdentifierMap, + ) -> Result<()>; +} + +struct ArtifactLocalApplyDriver<'a> { + module_graph: &'a ModuleGraph, + module_graph_cache: &'a ModuleGraphCacheArtifact, + dependency_exports_analysis_artifact: &'a mut DependencyExportsAnalysisArtifact, +} + +impl LocalApplyDriver for ArtifactLocalApplyDriver<'_> { + fn recollect( + &mut self, + exports_info_artifact: &ExportsInfoArtifact, + modules: &IdentifierSet, + ) -> Result<()> { + collector::collect_module_analysis( + self.module_graph, + self.module_graph_cache, + exports_info_artifact, + self.dependency_exports_analysis_artifact, + modules, + ) + } + + fn apply_flat( + &mut self, + exports_info_artifact: &mut ExportsInfoArtifact, + modules: &IdentifierSet, + changed_modules: &mut IdentifierSet, + dependencies: &mut IdentifierMap, + ) -> Result<()> { + apply_flat_local_exports_in_parallel( + self.module_graph, + exports_info_artifact, + self.dependency_exports_analysis_artifact, + modules, + changed_modules, + dependencies, + ) + } + + fn apply_structured( + &mut self, + exports_info_artifact: &mut ExportsInfoArtifact, + modules: &IdentifierSet, + changed_modules: &mut IdentifierSet, + dependencies: &mut IdentifierMap, + ) -> Result<()> { + apply_structured_local_exports_sequentially( + self.module_graph, + exports_info_artifact, + self.dependency_exports_analysis_artifact, + modules, + changed_modules, + dependencies, + ) + } +} + +fn apply_local_exports_with_driver( + driver: &mut impl LocalApplyDriver, + exports_info_artifact: &mut ExportsInfoArtifact, + initial_modules: &IdentifierSet, +) -> Result<()> { + let mut batch = initial_modules.clone(); + let mut dependencies: IdentifierMap = + IdentifierMap::with_capacity_and_hasher(batch.len(), Default::default()); + + while !batch.is_empty() { + let modules = std::mem::take(&mut batch); + let mut changed_modules = + IdentifierSet::with_capacity_and_hasher(modules.len(), Default::default()); + + driver.recollect(exports_info_artifact, &modules)?; + driver.apply_flat( + exports_info_artifact, + &modules, + &mut changed_modules, + &mut dependencies, + )?; + driver.apply_structured( + exports_info_artifact, + &modules, + &mut changed_modules, + &mut dependencies, + )?; + + batch.extend(changed_modules.into_iter().flat_map(|module_identifier| { + dependencies + .get(&module_identifier) + .into_iter() + .flat_map(|dependents| dependents.iter()) + .copied() + })); + } + + Ok(()) +} + +#[cfg(test)] +fn apply_local_exports_with( + exports_info_artifact: &mut ExportsInfoArtifact, + initial_modules: &IdentifierSet, + recollect: TRecollect, + apply_flat: TFlat, + apply_structured: TStructured, +) -> Result<()> +where + TRecollect: FnMut(&ExportsInfoArtifact, &IdentifierSet) -> Result<()>, + TFlat: FnMut( + &mut ExportsInfoArtifact, + &IdentifierSet, + &mut IdentifierSet, + &mut IdentifierMap, + ) -> Result<()>, + TStructured: FnMut( + &mut ExportsInfoArtifact, + &IdentifierSet, + &mut IdentifierSet, + &mut IdentifierMap, + ) -> Result<()>, +{ + struct ClosureLocalApplyDriver { + recollect: TRecollect, + apply_flat: TFlat, + apply_structured: TStructured, + } + + impl LocalApplyDriver + for ClosureLocalApplyDriver + where + TRecollect: FnMut(&ExportsInfoArtifact, &IdentifierSet) -> Result<()>, + TFlat: FnMut( + &mut ExportsInfoArtifact, + &IdentifierSet, + &mut IdentifierSet, + &mut IdentifierMap, + ) -> Result<()>, + TStructured: FnMut( + &mut ExportsInfoArtifact, + &IdentifierSet, + &mut IdentifierSet, + &mut IdentifierMap, + ) -> Result<()>, + { + fn recollect( + &mut self, + exports_info_artifact: &ExportsInfoArtifact, + modules: &IdentifierSet, + ) -> Result<()> { + (self.recollect)(exports_info_artifact, modules) + } + + fn apply_flat( + &mut self, + exports_info_artifact: &mut ExportsInfoArtifact, + modules: &IdentifierSet, + changed_modules: &mut IdentifierSet, + dependencies: &mut IdentifierMap, + ) -> Result<()> { + (self.apply_flat)( + exports_info_artifact, + modules, + changed_modules, + dependencies, + ) + } + + fn apply_structured( + &mut self, + exports_info_artifact: &mut ExportsInfoArtifact, + modules: &IdentifierSet, + changed_modules: &mut IdentifierSet, + dependencies: &mut IdentifierMap, + ) -> Result<()> { + (self.apply_structured)( + exports_info_artifact, + modules, + changed_modules, + dependencies, + ) + } + } + + let mut driver = ClosureLocalApplyDriver { + recollect, + apply_flat, + apply_structured, + }; + apply_local_exports_with_driver(&mut driver, exports_info_artifact, initial_modules) +} + +fn apply_flat_local_exports_in_parallel( + module_graph: &ModuleGraph, + exports_info_artifact: &mut ExportsInfoArtifact, + dependency_exports_analysis_artifact: &DependencyExportsAnalysisArtifact, + modules: &IdentifierSet, + changed_modules: &mut IdentifierSet, + dependencies: &mut IdentifierMap, +) -> Result<()> { + let flat_tasks = modules + .par_iter() + .filter_map(|module_identifier| { + let module_analysis = dependency_exports_analysis_artifact.module(module_identifier)?; + if module_analysis.flat_local_apply().is_empty() { + return None; + } + + let mut changed = false; + let mut changed_dependencies = Vec::new(); + let mut exports_info = exports_info_artifact + .get_exports_info_data(module_identifier) + .clone(); + for (dep_id, exports_spec) in module_analysis.flat_local_apply() { + let (task_changed, task_changed_dependencies) = process_exports_spec_without_nested( + module_graph, + exports_info_artifact, + module_identifier, + *dep_id, + exports_spec, + &mut exports_info, + ); + changed |= task_changed; + changed_dependencies.extend(task_changed_dependencies); + } + + Some(( + *module_identifier, + changed, + changed_dependencies, + exports_info, + )) + }) + .collect::>(); + + for (module_identifier, changed, changed_dependencies, exports_info) in flat_tasks { + if changed { + changed_modules.insert(module_identifier); + } + for (target_module, dependent_module) in changed_dependencies { + dependencies + .entry(target_module) + .or_default() + .insert(dependent_module); + } + exports_info_artifact.set_exports_info_by_id(exports_info.id(), exports_info); + } + + Ok(()) +} + +fn apply_flat_local_exports_in_parallel_no_deps( + module_graph: &ModuleGraph, + exports_info_artifact: &mut ExportsInfoArtifact, + dependency_exports_analysis_artifact: &DependencyExportsAnalysisArtifact, + modules: &IdentifierSet, + changed_modules: &mut IdentifierSet, +) -> Result<()> { + let flat_tasks = modules + .par_iter() + .filter_map(|module_identifier| { + let module_analysis = dependency_exports_analysis_artifact.module(module_identifier)?; + if module_analysis.flat_local_apply().is_empty() { + return None; + } + + let mut changed = false; + let mut exports_info = exports_info_artifact + .get_exports_info_data(module_identifier) + .clone(); + for (dep_id, exports_spec) in module_analysis.flat_local_apply() { + changed |= process_exports_spec_without_nested_no_deps( + module_graph, + exports_info_artifact, + module_identifier, + *dep_id, + exports_spec, + &mut exports_info, + ); + } + + Some((*module_identifier, changed, exports_info)) + }) + .collect::>(); + + for (module_identifier, changed, exports_info) in flat_tasks { + if changed { + changed_modules.insert(module_identifier); + } + exports_info_artifact.set_exports_info_by_id(exports_info.id(), exports_info); + } + + Ok(()) +} + +fn apply_structured_local_exports_sequentially( + module_graph: &ModuleGraph, + exports_info_artifact: &mut ExportsInfoArtifact, + dependency_exports_analysis_artifact: &DependencyExportsAnalysisArtifact, + modules: &IdentifierSet, + changed_modules: &mut IdentifierSet, + dependencies: &mut IdentifierMap, +) -> Result<()> { + for module_identifier in modules { + let Some(module_analysis) = dependency_exports_analysis_artifact.module(module_identifier) + else { + continue; + }; + let mut changed = false; + for (dep_id, exports_spec) in module_analysis.structured_local_apply() { + let (task_changed, changed_dependencies) = process_exports_spec( + module_graph, + exports_info_artifact, + module_identifier, + *dep_id, + exports_spec, + ); + changed |= task_changed; + for (target_module, dependent_module) in changed_dependencies { + dependencies + .entry(target_module) + .or_default() + .insert(dependent_module); + } + } + if changed { + changed_modules.insert(*module_identifier); + } + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use rspack_core::{ExportProvided, ModuleIdentifier}; + use swc_core::ecma::atoms::Atom; + + use super::*; + + fn sorted_modules(modules: &IdentifierSet) -> Vec { + let mut modules = modules.iter().copied().collect::>(); + modules.sort_unstable(); + modules + } + + #[test] + fn apply_local_exports_recollects_requeued_modules_against_latest_exports_info() -> Result<()> { + let root = ModuleIdentifier::from("root"); + let dependent = ModuleIdentifier::from("dependent"); + let export_name = Atom::from("value"); + let mut exports_info_artifact = ExportsInfoArtifact::default(); + exports_info_artifact.new_exports_info(root); + exports_info_artifact.new_exports_info(dependent); + + let mut initial_modules = IdentifierSet::default(); + initial_modules.insert(root); + + let mut recollected_batches = Vec::new(); + let mut dependent_saw_latest_exports = false; + + apply_local_exports_with( + &mut exports_info_artifact, + &initial_modules, + |exports_info_artifact, modules| { + recollected_batches.push(sorted_modules(modules)); + if modules.contains(&dependent) { + dependent_saw_latest_exports = matches!( + exports_info_artifact + .get_exports_info_data(&root) + .named_exports(&export_name) + .and_then(|export_info| export_info.provided()), + Some(ExportProvided::Provided) + ); + } + Ok(()) + }, + |exports_info_artifact, modules, changed_modules, dependencies| { + if modules.contains(&root) { + exports_info_artifact + .get_exports_info_data_mut(&root) + .ensure_owned_export_info(&export_name) + .set_provided(Some(ExportProvided::Provided)); + changed_modules.insert(root); + dependencies.entry(root).or_default().insert(dependent); + } + Ok(()) + }, + |_, _, _, _| Ok(()), + )?; + + assert_eq!( + recollected_batches, + vec![vec![root], vec![dependent]], + "the requeued dependent should be recollected in its own follow-up batch" + ); + assert!( + dependent_saw_latest_exports, + "the dependent recollection should observe the root export after the first batch applies" + ); + + Ok(()) + } +} diff --git a/crates/rspack_plugin_javascript/src/plugin/flag_dependency_exports_plugin/propagation.rs b/crates/rspack_plugin_javascript/src/plugin/flag_dependency_exports_plugin/propagation.rs new file mode 100644 index 000000000000..e4a3c4171dfe --- /dev/null +++ b/crates/rspack_plugin_javascript/src/plugin/flag_dependency_exports_plugin/propagation.rs @@ -0,0 +1,479 @@ +use rayon::prelude::*; +use rspack_collections::IdentifierSet; +use rspack_core::{ + DeferredReexportSpec, DependencyExportsAnalysisArtifact, ExportNameOrSpec, ExportSpec, + ExportsInfo, ExportsInfoArtifact, ExportsInfoData, ExportsInfoRead, ExportsOfExportsSpec, + ExportsSpec, ModuleGraph, ModuleGraphCacheArtifact, ModuleIdentifier, ProvidedExports, +}; +use rspack_error::Result; +use rustc_hash::FxHashMap; + +use super::{collector, local_apply, process_exports_spec_without_nested}; + +pub(super) fn propagate_deferred_reexports( + module_graph: &ModuleGraph, + module_graph_cache: &ModuleGraphCacheArtifact, + exports_info_artifact: &mut ExportsInfoArtifact, + dependency_exports_analysis_artifact: &mut DependencyExportsAnalysisArtifact, +) -> Result<()> { + if !dependency_exports_analysis_artifact.has_deferred_reexports() { + dependency_exports_analysis_artifact.clear_all_dirty(); + return Ok(()); + } + if dependency_exports_analysis_artifact.topology_dirty() { + dependency_exports_analysis_artifact.rebuild_topology(); + } + let mut changed_modules = IdentifierSet::default(); + let wave_count = dependency_exports_analysis_artifact + .topology() + .waves() + .len(); + for wave_index in 0..wave_count { + let refresh_modules = { + let wave_modules = dependency_exports_analysis_artifact + .topology() + .wave_modules(wave_index); + select_wave_local_refresh_modules( + dependency_exports_analysis_artifact, + wave_modules, + &changed_modules, + ) + }; + if !refresh_modules.is_empty() { + collector::collect_module_analysis_with_reuse( + module_graph, + module_graph_cache, + exports_info_artifact, + dependency_exports_analysis_artifact, + &refresh_modules, + )?; + changed_modules = local_apply::apply_local_exports_once( + module_graph, + exports_info_artifact, + dependency_exports_analysis_artifact, + &refresh_modules, + )?; + } else { + changed_modules.clear(); + } + let deferred_wave = dependency_exports_analysis_artifact + .topology() + .deferred_wave(wave_index) + .to_vec(); + let wave_updates = deferred_wave + .par_iter() + .map(|scc_id| { + resolve_scc_until_fixed_point( + *scc_id, + module_graph, + exports_info_artifact, + dependency_exports_analysis_artifact, + ) + }) + .collect::>>()?; + + for module_update in wave_updates.into_iter().flatten() { + if module_update.changed { + changed_modules.insert(module_update.module_identifier); + exports_info_artifact + .set_exports_info_by_id(module_update.exports_info.id(), module_update.exports_info); + } + } + } + dependency_exports_analysis_artifact.clear_all_dirty(); + + Ok(()) +} + +fn select_wave_local_refresh_modules( + dependency_exports_analysis_artifact: &DependencyExportsAnalysisArtifact, + wave_modules: &IdentifierSet, + changed_modules: &IdentifierSet, +) -> IdentifierSet { + if changed_modules.is_empty() { + return IdentifierSet::default(); + } + + let mut refresh_modules = wave_modules + .iter() + .filter(|module_identifier| { + dependency_exports_analysis_artifact + .module(module_identifier) + .is_some_and(|module_analysis| !module_analysis.deferred_reexports().is_empty()) + }) + .copied() + .collect::(); + + for changed_module in changed_modules { + if let Some(dependents) = + dependency_exports_analysis_artifact.flat_dependents_of(changed_module) + { + refresh_modules.extend( + dependents + .iter() + .filter(|dependent| wave_modules.contains(dependent)) + .copied(), + ); + } + } + + refresh_modules +} + +#[cfg(test)] +fn process_waves_in_parallel( + waves: &[Vec], + mut build_wave_state: TSnapshot, + resolve_scc: TResolve, + mut apply_wave: TApply, +) -> Result<()> +where + TWaveState: Sync, + TPatch: Send, + TSnapshot: FnMut(&[usize]) -> TWaveState, + TResolve: Fn(&TWaveState, usize) -> Result> + Sync, + TApply: FnMut(&[usize], Vec) -> Result<()>, +{ + for wave in waves { + let wave_state = build_wave_state(wave); + let wave_patches = wave + .par_iter() + .map(|scc_id| resolve_scc(&wave_state, *scc_id)) + .collect::>>()? + .into_iter() + .flatten() + .collect::>(); + apply_wave(wave, wave_patches)?; + } + + Ok(()) +} + +struct PropagationModuleUpdate { + changed: bool, + module_identifier: ModuleIdentifier, + exports_info: ExportsInfoData, +} + +struct PatchedSccExportsInfoRead<'a> { + source: &'a ExportsInfoArtifact, + patches: &'a FxHashMap, +} + +impl ExportsInfoRead for PatchedSccExportsInfoRead<'_> { + fn get_exports_info(&self, module_identifier: &ModuleIdentifier) -> ExportsInfo { + self.source.get_exports_info(module_identifier) + } + + fn get_exports_info_by_id(&self, id: &ExportsInfo) -> &ExportsInfoData { + self + .patches + .get(id) + .unwrap_or_else(|| self.source.get_exports_info_by_id(id)) + } +} + +fn resolve_scc_until_fixed_point( + scc_id: usize, + module_graph: &ModuleGraph, + exports_info_artifact: &ExportsInfoArtifact, + dependency_exports_analysis_artifact: &DependencyExportsAnalysisArtifact, +) -> Result> { + let scc_modules = dependency_exports_analysis_artifact + .topology() + .scc_modules(scc_id) + .to_vec(); + let mut patches = FxHashMap::default(); + + loop { + let read_state = PatchedSccExportsInfoRead { + source: exports_info_artifact, + patches: &patches, + }; + let mut updates = Vec::new(); + let mut changed = false; + let mut pending_patches = Vec::new(); + + for module_identifier in &scc_modules { + let Some(module_analysis) = dependency_exports_analysis_artifact.module(module_identifier) + else { + continue; + }; + if module_analysis.deferred_reexports().is_empty() { + continue; + } + + let mut module_changed = false; + let exports_info_id = read_state.get_exports_info(module_identifier); + let mut exports_info = read_state.get_exports_info_by_id(&exports_info_id).clone(); + for deferred_reexport in module_analysis.deferred_reexports() { + let Some(exports_spec) = + deferred_reexport_as_exports_spec(module_graph, &read_state, deferred_reexport) + else { + continue; + }; + let (propagation_changed, _changed_dependencies) = process_exports_spec_without_nested( + module_graph, + &read_state, + module_identifier, + deferred_reexport.dep_id, + &exports_spec, + &mut exports_info, + ); + module_changed |= propagation_changed; + } + + changed |= module_changed; + if module_changed { + pending_patches.push(exports_info.clone()); + } + updates.push(PropagationModuleUpdate { + changed: module_changed, + module_identifier: *module_identifier, + exports_info, + }); + } + + if !changed { + return Ok( + updates + .into_iter() + .filter(|module_update| patches.contains_key(&module_update.exports_info.id())) + .map(|module_update| PropagationModuleUpdate { + changed: true, + ..module_update + }) + .collect(), + ); + } + for exports_info in pending_patches { + patches.insert(exports_info.id(), exports_info); + } + } +} + +fn deferred_reexport_as_exports_spec( + module_graph: &ModuleGraph, + exports_info_read: &T, + deferred_reexport: &DeferredReexportSpec, +) -> Option { + let from = module_graph + .connection_by_dependency_id(&deferred_reexport.dep_id) + .cloned()?; + if let Some(star_exports) = &deferred_reexport.star_exports { + let provided_exports = exports_info_read + .get_prefetched_exports_info( + &deferred_reexport.target_module, + rspack_core::PrefetchExportsInfoMode::Default, + ) + .get_provided_exports(); + let exports = match provided_exports { + ProvidedExports::Unknown | ProvidedExports::ProvidedAll => { + ExportsOfExportsSpec::UnknownExports + } + ProvidedExports::ProvidedNames(names) => { + let mut exports = names + .into_iter() + .filter(|name| { + !star_exports.ignored_exports.contains(name) + && !star_exports.hidden_exports.contains(name) + }) + .map(|name| { + let mut export_path = star_exports.export_name_prefix.clone(); + export_path.push(name.clone()); + ExportNameOrSpec::ExportSpec(ExportSpec { + name, + from: Some(from.clone()), + export: Some(rspack_core::Nullable::Value(export_path)), + hidden: Some(false), + ..Default::default() + }) + }) + .collect::>(); + + exports.extend(star_exports.hidden_exports.iter().cloned().map(|name| { + let mut export_path = star_exports.export_name_prefix.clone(); + export_path.push(name.clone()); + ExportNameOrSpec::ExportSpec(ExportSpec { + name, + from: Some(from.clone()), + export: Some(rspack_core::Nullable::Value(export_path)), + hidden: Some(true), + ..Default::default() + }) + })); + ExportsOfExportsSpec::Names(exports) + } + }; + + let hide_export = + (!star_exports.hidden_exports.is_empty()).then(|| star_exports.hidden_exports.clone()); + let exclude_exports = { + let mut excluded = star_exports.ignored_exports.clone(); + excluded.extend(star_exports.hidden_exports.iter().cloned()); + (!excluded.is_empty()).then_some(excluded) + }; + + return Some(ExportsSpec { + exports, + priority: deferred_reexport.priority, + can_mangle: deferred_reexport.can_mangle, + terminal_binding: Some(deferred_reexport.terminal_binding), + from: Some(from), + hide_export, + exclude_exports, + dependencies: Some(vec![deferred_reexport.target_module]), + ..Default::default() + }); + } + let exports = deferred_reexport + .items + .iter() + .map(|item| { + ExportNameOrSpec::ExportSpec(ExportSpec { + name: item.exposed_name.clone(), + from: Some(from.clone()), + export: match &item.target_path { + rspack_core::Nullable::Null => None, + rspack_core::Nullable::Value(path) => Some(rspack_core::Nullable::Value(path.clone())), + }, + hidden: Some(item.hidden), + ..Default::default() + }) + }) + .collect::>(); + + Some(ExportsSpec { + exports: ExportsOfExportsSpec::Names(exports), + priority: deferred_reexport.priority, + can_mangle: deferred_reexport.can_mangle, + terminal_binding: Some(deferred_reexport.terminal_binding), + dependencies: Some(vec![deferred_reexport.target_module]), + ..Default::default() + }) +} + +#[cfg(test)] +fn propagation_waves( + dependency_exports_analysis_artifact: &DependencyExportsAnalysisArtifact, +) -> Vec>> { + dependency_exports_analysis_artifact + .topology() + .waves() + .iter() + .map(|wave| { + wave + .iter() + .map(|scc_id| { + dependency_exports_analysis_artifact + .topology() + .scc_modules(*scc_id) + .to_vec() + }) + .collect() + }) + .collect() +} + +#[cfg(test)] +mod tests { + use std::sync::{ + Arc, Mutex, + atomic::{AtomicUsize, Ordering}, + }; + + use rspack_core::{ + DeferredReexportItem, DeferredReexportSpec, DependencyExportsAnalysisArtifact, DependencyId, + ModuleDependencyExportsAnalysis, ModuleIdentifier, Nullable, + }; + + use super::*; + + #[test] + fn propagate_runs_independent_sccs_in_the_same_wave_and_converges_per_scc() { + let mut artifact = DependencyExportsAnalysisArtifact::default(); + let left = ModuleIdentifier::from("left"); + let right = ModuleIdentifier::from("right"); + let root = ModuleIdentifier::from("root"); + + artifact.replace_module(left, ModuleDependencyExportsAnalysis::with_targets([])); + artifact.replace_module(right, ModuleDependencyExportsAnalysis::with_targets([])); + artifact.replace_module( + root, + ModuleDependencyExportsAnalysis::with_staged_analysis( + Arc::<[DependencyId]>::from([]), + false, + [left, right], + [], + [], + [], + [DeferredReexportSpec::new( + left, + DependencyId::from(7), + vec![DeferredReexportItem { + exposed_name: "value".into(), + target_path: Nullable::Value(vec!["value".into()]), + hidden: false, + }], + )], + ), + ); + artifact.rebuild_topology(); + + let summary = propagation_waves(&artifact); + assert_eq!(summary[0].len(), 2); + assert_eq!(summary[1].len(), 1); + assert_eq!( + artifact + .module(&root) + .expect("root analysis should exist") + .deferred_reexports() + .len(), + 1 + ); + } + + #[test] + fn propagate_builds_one_shared_snapshot_per_wave() -> Result<()> { + let waves = vec![vec![0, 1], vec![2]]; + let snapshot_builds = AtomicUsize::new(0); + let resolved = Mutex::new(Vec::new()); + let applied = Mutex::new(Vec::new()); + + process_waves_in_parallel( + &waves, + |_| snapshot_builds.fetch_add(1, Ordering::SeqCst) + 1, + |snapshot_id, scc_id| { + resolved + .lock() + .expect("should lock") + .push((*snapshot_id, scc_id)); + Ok(vec![(scc_id, *snapshot_id)]) + }, + |_wave, patches| { + applied.lock().expect("should lock").push(patches); + Ok(()) + }, + )?; + + assert_eq!( + snapshot_builds.load(Ordering::SeqCst), + 2, + "each wave should build exactly one shared snapshot" + ); + let mut resolved = resolved.lock().expect("should lock").clone(); + resolved.sort_unstable(); + assert_eq!( + resolved, + vec![(1, 0), (1, 1), (2, 2)], + "all SCCs in the first wave should resolve from the same shared snapshot" + ); + assert_eq!( + applied.lock().expect("should lock").clone(), + vec![vec![(0, 1), (1, 1)], vec![(2, 2)]], + "patches should apply only after the wave-wide parallel work completes" + ); + + Ok(()) + } +} diff --git a/crates/rspack_plugin_javascript/src/plugin/flag_dependency_exports_plugin/types.rs b/crates/rspack_plugin_javascript/src/plugin/flag_dependency_exports_plugin/types.rs new file mode 100644 index 000000000000..3dcfc3debfde --- /dev/null +++ b/crates/rspack_plugin_javascript/src/plugin/flag_dependency_exports_plugin/types.rs @@ -0,0 +1,58 @@ +use rspack_core::{ + DeferredReexportSpec, DependencyId, ExportsOfExportsSpec, ExportsProcessing, ExportsSpec, +}; + +#[derive(Debug, Default)] +pub(super) struct NormalizedModuleAnalysis { + pub local_apply: Vec, + pub deferred_reexports: Vec, +} + +impl NormalizedModuleAnalysis { + pub(super) fn from_local(spec: ExportsSpec) -> Self { + Self { + local_apply: vec![spec], + deferred_reexports: Vec::new(), + } + } + + pub(super) fn from_deferred( + mut spec: ExportsSpec, + deferred_reexports: Vec, + ) -> Self { + spec.processing = ExportsProcessing::Immediate; + + let local_apply = if has_local_apply_work(&spec) { + vec![spec] + } else { + Vec::new() + }; + + Self { + local_apply, + deferred_reexports, + } + } + + pub(super) fn bind_local_apply_with_dep_id( + dep_id: DependencyId, + local_apply: Vec, + ) -> Vec<(DependencyId, ExportsSpec)> { + local_apply.into_iter().map(|spec| (dep_id, spec)).collect() + } +} + +fn has_local_apply_work(spec: &ExportsSpec) -> bool { + (match &spec.exports { + ExportsOfExportsSpec::UnknownExports => true, + ExportsOfExportsSpec::NoExports => false, + ExportsOfExportsSpec::Names(exports) => !exports.is_empty(), + }) || spec + .hide_export + .as_ref() + .is_some_and(|exports| !exports.is_empty()) + || spec + .exclude_exports + .as_ref() + .is_some_and(|exports| !exports.is_empty()) +} diff --git a/crates/rspack_plugin_javascript/src/plugin/infer_async_modules_plugin.rs b/crates/rspack_plugin_javascript/src/plugin/infer_async_modules_plugin.rs index b464418306da..76a3d55b5be9 100644 --- a/crates/rspack_plugin_javascript/src/plugin/infer_async_modules_plugin.rs +++ b/crates/rspack_plugin_javascript/src/plugin/infer_async_modules_plugin.rs @@ -1,8 +1,8 @@ use rayon::prelude::*; use rspack_collections::{IdentifierLinkedSet, IdentifierMap, IdentifierSet}; use rspack_core::{ - AsyncModulesArtifact, Compilation, CompilationFinishModules, DependencyType, ExportsInfoArtifact, - Logger, ModuleGraph, Plugin, + AsyncModulesArtifact, Compilation, CompilationFinishModules, DependencyExportsAnalysisArtifact, + DependencyType, ExportsInfoArtifact, Logger, ModuleGraph, Plugin, incremental::{IncrementalPasses, Mutation, Mutations}, }; use rspack_error::Result; @@ -18,6 +18,7 @@ async fn finish_modules( compilation: &Compilation, async_modules_artifact: &mut AsyncModulesArtifact, _exports_info_artifact: &mut ExportsInfoArtifact, + _dependency_exports_analysis_artifact: &mut DependencyExportsAnalysisArtifact, ) -> Result<()> { if let Some(mutations) = compilation .incremental diff --git a/crates/rspack_plugin_library/src/assign_library_plugin.rs b/crates/rspack_plugin_library/src/assign_library_plugin.rs index 4c7945ac504c..9e66911e28b3 100644 --- a/crates/rspack_plugin_library/src/assign_library_plugin.rs +++ b/crates/rspack_plugin_library/src/assign_library_plugin.rs @@ -445,6 +445,7 @@ async fn finish_modules( compilation: &Compilation, _async_modules_artifact: &mut AsyncModulesArtifact, exports_info_artifact: &mut ExportsInfoArtifact, + _dependency_exports_analysis_artifact: &mut rspack_core::DependencyExportsAnalysisArtifact, ) -> Result<()> { let module_graph = compilation.get_module_graph(); let mut runtime_info = Vec::with_capacity(compilation.entries.len()); diff --git a/crates/rspack_plugin_library/src/export_property_library_plugin.rs b/crates/rspack_plugin_library/src/export_property_library_plugin.rs index 6f897d961ed5..601510ae8bcd 100644 --- a/crates/rspack_plugin_library/src/export_property_library_plugin.rs +++ b/crates/rspack_plugin_library/src/export_property_library_plugin.rs @@ -117,6 +117,7 @@ async fn finish_modules( compilation: &Compilation, _async_modules_artifact: &mut AsyncModulesArtifact, exports_info_artifact: &mut ExportsInfoArtifact, + _dependency_exports_analysis_artifact: &mut rspack_core::DependencyExportsAnalysisArtifact, ) -> Result<()> { let module_graph = compilation.get_module_graph(); let mut runtime_info = Vec::with_capacity(compilation.entries.len()); diff --git a/crates/rspack_plugin_mf/src/container/module_federation_runtime_plugin.rs b/crates/rspack_plugin_mf/src/container/module_federation_runtime_plugin.rs index edd4d7f54660..6a954330f0d5 100644 --- a/crates/rspack_plugin_mf/src/container/module_federation_runtime_plugin.rs +++ b/crates/rspack_plugin_mf/src/container/module_federation_runtime_plugin.rs @@ -98,6 +98,7 @@ async fn finish_modules( compilation: &Compilation, async_modules_artifact: &mut AsyncModulesArtifact, _exports_info_artifact: &mut ExportsInfoArtifact, + _dependency_exports_analysis_artifact: &mut rspack_core::DependencyExportsAnalysisArtifact, ) -> Result<()> { if !self.options.experiments.async_startup { return Ok(()); diff --git a/crates/rspack_plugin_progress/src/lib.rs b/crates/rspack_plugin_progress/src/lib.rs index 7a017149c1a1..f64175a66ba1 100644 --- a/crates/rspack_plugin_progress/src/lib.rs +++ b/crates/rspack_plugin_progress/src/lib.rs @@ -442,6 +442,7 @@ async fn finish_modules( _compilation: &Compilation, _async_modules_artifact: &mut AsyncModulesArtifact, _exports_info_artifact: &mut ExportsInfoArtifact, + _dependency_exports_analysis_artifact: &mut rspack_core::DependencyExportsAnalysisArtifact, ) -> Result<()> { self.sealing_hooks_report("finish modules", 0).await } diff --git a/tests/rspack-test/esmOutputCases/externals/runtime-decide/__snapshots__/esm.snap.txt b/tests/rspack-test/esmOutputCases/externals/runtime-decide/__snapshots__/esm.snap.txt index abd330b4d502..355ab46f6bf2 100644 --- a/tests/rspack-test/esmOutputCases/externals/runtime-decide/__snapshots__/esm.snap.txt +++ b/tests/rspack-test/esmOutputCases/externals/runtime-decide/__snapshots__/esm.snap.txt @@ -1,7 +1,7 @@ ```mjs title=main.mjs +import { readFile } from "fs"; import { __webpack_require__ } from "./runtime.mjs"; -import * as __rspack_external_fs from "fs"; export * from "fs"; export * from "path"; __webpack_require__.add({ @@ -18,36 +18,17 @@ def(exports) }, -"./runtime-decide.js" -/*!***************************!*\ - !*** ./runtime-decide.js ***! - \***************************/ -(__unused_rspack_module, __webpack_exports__, __webpack_require__) { -/* import */ var _dynamic_exports__rspack_import_0 = __webpack_require__(/*! ./dynamic-exports */ "./dynamic-exports.js"); -/* import */ var _dynamic_exports__rspack_import_0_default = /*#__PURE__*/__webpack_require__.n(_dynamic_exports__rspack_import_0); -if(__webpack_require__.o(_dynamic_exports__rspack_import_0, "readFile")) __webpack_require__.d(__webpack_exports__, { readFile: function() { return _dynamic_exports__rspack_import_0.readFile; } }); -/* import */ var fs__rspack_import_1 = __webpack_require__(/*! fs */ "fs"); -if(__webpack_require__.o(fs__rspack_import_1, "readFile")) __webpack_require__.d(__webpack_exports__, { readFile: function() { return fs__rspack_import_1.readFile; } }); +}); +// fs + +// ./runtime-decide.js // side effects console.log.bind() - -}, -"fs" -/*!*********************!*\ - !*** external "fs" ***! - \*********************/ -(module) { - -module.exports = __rspack_external_fs; - - -}, -}); // ./index.js -const runtime_decide = __webpack_require__("./runtime-decide.js"); +const dynamic_exports = __webpack_require__("./dynamic-exports.js"); @@ -58,7 +39,7 @@ it('should have correct exports as its exports is decided at runtime', async () const { resolve: nodeResolve } = await import(/* webpackIgnore: true */ 'path'); expect(resolve).toBe(nodeResolve) - expect(runtime_decide.readFile()).toBe(42) + expect(dynamic_exports.readFile()).toBe(42) }) @@ -89,36 +70,10 @@ return module.exports; // expose the modules object (__webpack_modules__) __webpack_require__.m = __webpack_modules__; -// webpack/runtime/compat_get_default_export -(() => { -// getDefaultExport function for compatibility with non-ESM modules -__webpack_require__.n = (module) => { - var getter = module && module.__esModule ? - () => (module['default']) : - () => (module); - __webpack_require__.d(getter, { a: getter }); - return getter; -}; - -})(); -// webpack/runtime/define_property_getters -(() => { -__webpack_require__.d = (exports, definition) => { - for(var key in definition) { - if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { - Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); - } - } -}; -})(); // webpack/runtime/esm_register_module (() => { __webpack_require__.add = function registerModules(modules) { Object.assign(__webpack_require__.m, modules) } -})(); -// webpack/runtime/has_own_property -(() => { -__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) })(); export { __webpack_require__ }; diff --git a/tests/rspack-test/esmOutputCases/namespace/conflict/__snapshots__/esm.snap.txt b/tests/rspack-test/esmOutputCases/namespace/conflict/__snapshots__/esm.snap.txt index b271e0fe5d9c..9f96033e16af 100644 --- a/tests/rspack-test/esmOutputCases/namespace/conflict/__snapshots__/esm.snap.txt +++ b/tests/rspack-test/esmOutputCases/namespace/conflict/__snapshots__/esm.snap.txt @@ -1,8 +1,26 @@ ```mjs title=main.mjs +import { __webpack_require__ } from "./runtime.mjs"; + +__webpack_require__.add({ +"./reexport.js" +/*!*********************!*\ + !*** ./reexport.js ***! + \*********************/ +(__unused_rspack_module, __webpack_exports__, __webpack_require__) { +__webpack_require__.d(__webpack_exports__, { + p: () => (/* reexport safe */ (/* inlined export _first_js__rspack_import_0 */1)) +}); + + + + +}, +}); // ./index.js +const reexport = __webpack_require__("./reexport.js"); -/* export default */ const index = ((/* inlined export .foo */1)); +/* export default */ const index = (reexport.p); it("should keep the first conflicting namespace export under a warning", async () => { const mod = await import(/* webpackIgnore: true */ "./main.mjs"); @@ -12,4 +30,53 @@ it("should keep the first conflicting namespace export under a warning", async ( export default index; +``` + +```mjs title=runtime.mjs + +var __webpack_modules__ = {}; +// The module cache +var __webpack_module_cache__ = {}; +// The require function +function __webpack_require__(moduleId) { +// Check if module is in cache +var cachedModule = __webpack_module_cache__[moduleId]; +if (cachedModule !== undefined) { +return cachedModule.exports; +} +// Create a new module (and put it into the cache) +var module = (__webpack_module_cache__[moduleId] = { +exports: {} +}); +// Execute the module function +__webpack_modules__[moduleId](module, module.exports, __webpack_require__); + +// Return the exports of the module +return module.exports; +} +// expose the modules object (__webpack_modules__) +__webpack_require__.m = __webpack_modules__; + +// webpack/runtime/define_property_getters +(() => { +__webpack_require__.d = (exports, definition) => { + for(var key in definition) { + if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { + Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); + } + } +}; +})(); +// webpack/runtime/esm_register_module +(() => { +__webpack_require__.add = function registerModules(modules) { Object.assign(__webpack_require__.m, modules) } + +})(); +// webpack/runtime/has_own_property +(() => { +__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) +})(); + +export { __webpack_require__ }; + ``` \ No newline at end of file diff --git a/tests/rspack-test/esmOutputCases/re-exports/deep-re-exports-esm-2/__snapshots__/esm.snap.txt b/tests/rspack-test/esmOutputCases/re-exports/deep-re-exports-esm-2/__snapshots__/esm.snap.txt index 9b998df06b57..a3a4ad77ec94 100644 --- a/tests/rspack-test/esmOutputCases/re-exports/deep-re-exports-esm-2/__snapshots__/esm.snap.txt +++ b/tests/rspack-test/esmOutputCases/re-exports/deep-re-exports-esm-2/__snapshots__/esm.snap.txt @@ -114,8 +114,8 @@ it('should re-export esm correctly', async () => { const lib_0 = __webpack_require__("./lib.js"); var lib_1 = lib_0.lib; -var lib2_0 = lib_0.lib2; var fs_0 = lib_0.fs; +var lib2_0 = lib_0.lib2; var lib3_0 = lib_0.lib3; var lib4_0 = lib_0.lib4; diff --git a/tests/rspack-test/esmOutputCases/re-exports/export-star-ambiguous/__snapshots__/esm.snap.txt b/tests/rspack-test/esmOutputCases/re-exports/export-star-ambiguous/__snapshots__/esm.snap.txt index 675aad12aef8..ea41af7e5c6c 100644 --- a/tests/rspack-test/esmOutputCases/re-exports/export-star-ambiguous/__snapshots__/esm.snap.txt +++ b/tests/rspack-test/esmOutputCases/re-exports/export-star-ambiguous/__snapshots__/esm.snap.txt @@ -1,9 +1,29 @@ ```mjs title=main.mjs -// ./b.js +import { __webpack_require__ } from "./runtime.mjs"; + +__webpack_require__.add({ +"./b.js" +/*!**************!*\ + !*** ./b.js ***! + \**************/ +() { const shared = 'from-b'; const onlyB = 'only-b'; -// ./index.js + +}, +"./index.js" +/*!******************!*\ + !*** ./index.js ***! + \******************/ +(__unused_rspack_module, __webpack_exports__, __webpack_require__) { +__webpack_require__.r(__webpack_exports__); +__webpack_require__.d(__webpack_exports__, { + onlyA: () => (/* reexport safe */ (/* inlined export _a__rspack_import_0 */"only-a")), + onlyB: () => (/* reexport safe */ (/* inlined export _b__rspack_import_1 */"only-b")), + shared: () => (/* reexport safe */ (/* inlined export _a__rspack_import_0 */"from-a")) +}); +/* import */ var _b__rspack_import_1 = __webpack_require__(/*! ./b */ "./b.js"); // Both a.js and b.js export 'shared'. Rspack resolves the ambiguity by // picking the first source (a.js) rather than excluding it per strict ES spec. // Non-overlapping names should still be re-exported normally. @@ -18,9 +38,73 @@ it('should handle ambiguous export star without crashing', async () => { expect(mod.shared).toBe('from-a'); }); -var index_onlyA = (/* inlined export .onlyA */"only-a"); -var index_onlyB = (/* inlined export .onlyB */"only-b"); -var index_shared = (/* inlined export .shared */"from-a"); -export { index_onlyA as onlyA, index_onlyB as onlyB, index_shared as shared }; + +}, +}); +const index = __webpack_require__("./index.js"); +var onlyA = index.onlyA; +var onlyB = index.onlyB; +var shared = index.shared; + +export { onlyA, onlyB, shared }; + +``` + +```mjs title=runtime.mjs + +var __webpack_modules__ = {}; +// The module cache +var __webpack_module_cache__ = {}; +// The require function +function __webpack_require__(moduleId) { +// Check if module is in cache +var cachedModule = __webpack_module_cache__[moduleId]; +if (cachedModule !== undefined) { +return cachedModule.exports; +} +// Create a new module (and put it into the cache) +var module = (__webpack_module_cache__[moduleId] = { +exports: {} +}); +// Execute the module function +__webpack_modules__[moduleId](module, module.exports, __webpack_require__); + +// Return the exports of the module +return module.exports; +} +// expose the modules object (__webpack_modules__) +__webpack_require__.m = __webpack_modules__; + +// webpack/runtime/define_property_getters +(() => { +__webpack_require__.d = (exports, definition) => { + for(var key in definition) { + if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { + Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); + } + } +}; +})(); +// webpack/runtime/esm_register_module +(() => { +__webpack_require__.add = function registerModules(modules) { Object.assign(__webpack_require__.m, modules) } + +})(); +// webpack/runtime/has_own_property +(() => { +__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) +})(); +// webpack/runtime/make_namespace_object +(() => { +// define __esModule on exports +__webpack_require__.r = (exports) => { + if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { + Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); + } + Object.defineProperty(exports, '__esModule', { value: true }); +}; +})(); + +export { __webpack_require__ }; ``` \ No newline at end of file diff --git a/tests/rspack-test/esmOutputCases/re-exports/re-export-external/__snapshots__/esm.snap.txt b/tests/rspack-test/esmOutputCases/re-exports/re-export-external/__snapshots__/esm.snap.txt index 8c7fe47a1e53..1cc44d266978 100644 --- a/tests/rspack-test/esmOutputCases/re-exports/re-export-external/__snapshots__/esm.snap.txt +++ b/tests/rspack-test/esmOutputCases/re-exports/re-export-external/__snapshots__/esm.snap.txt @@ -1,9 +1,26 @@ ```mjs title=main.mjs -import * as __rspack_external_fs from "fs"; +import { __webpack_require__ } from "./runtime.mjs"; -// fs +import * as __rspack_external_fs from "fs"; +__webpack_require__.add({ +"./index.js" +/*!******************!*\ + !*** ./index.js ***! + \******************/ +(__unused_rspack_module, __webpack_exports__, __webpack_require__) { +__webpack_require__.r(__webpack_exports__); +__webpack_require__.d(__webpack_exports__, { + fs: () => (/* reexport safe */ fs__rspack_import_0["default"]), + named: () => (/* reexport safe */ (/* inlined export _lib__rspack_import_1 */42)), + r: () => (/* reexport safe */ fs__rspack_import_0.readFile), + readFile: () => (/* reexport safe */ fs__rspack_import_0.readFile), + starExports: () => (/* reexport safe */ (/* inlined export _lib2__rspack_import_2 */42)) +}); +/* import */ var fs__rspack_import_0 = __webpack_require__(/*! fs */ "fs"); -// ./index.js +/* reexport */ var __rspack_reexport = {}; +/* reexport */ for( const __rspack_import_key in fs__rspack_import_0) if(["r","named","default","fs","starExports","readFile"].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] =() => fs__rspack_import_0[__rspack_import_key] +/* reexport */ __webpack_require__.d(__webpack_exports__, __rspack_reexport); @@ -25,12 +42,86 @@ it('should compile and import success', async () => { expect(readFile).toBeDefined() }) -var fs_0 = __rspack_external_fs["default"]; -var index_named = (/* inlined export .named */42); -var r_0 = __rspack_external_fs.readFile; -var readFile_0 = __rspack_external_fs.readFile; -var index_starExports = (/* inlined export .starExports */42); -export { fs_0 as fs, index_named as named, index_starExports as starExports, r_0 as r, readFile_0 as readFile }; + +}, +"fs" +/*!*********************!*\ + !*** external "fs" ***! + \*********************/ +(module) { + +module.exports = __rspack_external_fs; + + +}, +}); +const index = __webpack_require__("./index.js"); +var fs = index.fs; +var named = index.named; +var r = index.r; +var readFile = index.readFile; +var starExports = index.starExports; + +export { fs, named, r, readFile, starExports }; export * from "fs"; +``` + +```mjs title=runtime.mjs + +var __webpack_modules__ = {}; +// The module cache +var __webpack_module_cache__ = {}; +// The require function +function __webpack_require__(moduleId) { +// Check if module is in cache +var cachedModule = __webpack_module_cache__[moduleId]; +if (cachedModule !== undefined) { +return cachedModule.exports; +} +// Create a new module (and put it into the cache) +var module = (__webpack_module_cache__[moduleId] = { +exports: {} +}); +// Execute the module function +__webpack_modules__[moduleId](module, module.exports, __webpack_require__); + +// Return the exports of the module +return module.exports; +} +// expose the modules object (__webpack_modules__) +__webpack_require__.m = __webpack_modules__; + +// webpack/runtime/define_property_getters +(() => { +__webpack_require__.d = (exports, definition) => { + for(var key in definition) { + if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { + Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); + } + } +}; +})(); +// webpack/runtime/esm_register_module +(() => { +__webpack_require__.add = function registerModules(modules) { Object.assign(__webpack_require__.m, modules) } + +})(); +// webpack/runtime/has_own_property +(() => { +__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) +})(); +// webpack/runtime/make_namespace_object +(() => { +// define __esModule on exports +__webpack_require__.r = (exports) => { + if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { + Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); + } + Object.defineProperty(exports, '__esModule', { value: true }); +}; +})(); + +export { __webpack_require__ }; + ``` \ No newline at end of file diff --git a/tests/rspack-test/normalCases/esm/reexport-wave-parallel/index.js b/tests/rspack-test/normalCases/esm/reexport-wave-parallel/index.js new file mode 100644 index 000000000000..45081115cef7 --- /dev/null +++ b/tests/rspack-test/normalCases/esm/reexport-wave-parallel/index.js @@ -0,0 +1,9 @@ +import { leftValue, meta } from "./root"; +import { rightValue } from "./right"; + +it("should keep sibling reexport waves stable", () => { + expect(leftValue).toBe("left"); + expect(rightValue).toBe("right"); + expect(meta.leftValueUsed).toBe(true); + expect(meta.rightValueUsed).toBe(false); +}); diff --git a/tests/rspack-test/normalCases/esm/reexport-wave-parallel/leaf-left.js b/tests/rspack-test/normalCases/esm/reexport-wave-parallel/leaf-left.js new file mode 100644 index 000000000000..24b56abac2fc --- /dev/null +++ b/tests/rspack-test/normalCases/esm/reexport-wave-parallel/leaf-left.js @@ -0,0 +1 @@ +export const leftValue = "left"; diff --git a/tests/rspack-test/normalCases/esm/reexport-wave-parallel/leaf-right.js b/tests/rspack-test/normalCases/esm/reexport-wave-parallel/leaf-right.js new file mode 100644 index 000000000000..6834b8a066fd --- /dev/null +++ b/tests/rspack-test/normalCases/esm/reexport-wave-parallel/leaf-right.js @@ -0,0 +1 @@ +export const rightValue = "right"; diff --git a/tests/rspack-test/normalCases/esm/reexport-wave-parallel/left.js b/tests/rspack-test/normalCases/esm/reexport-wave-parallel/left.js new file mode 100644 index 000000000000..8f5bc09ead85 --- /dev/null +++ b/tests/rspack-test/normalCases/esm/reexport-wave-parallel/left.js @@ -0,0 +1,2 @@ +export { leftValue } from "./leaf-left"; +export const leftValueUsed = __webpack_exports_info__.leftValue.used; diff --git a/tests/rspack-test/normalCases/esm/reexport-wave-parallel/right.js b/tests/rspack-test/normalCases/esm/reexport-wave-parallel/right.js new file mode 100644 index 000000000000..bc38f145aa53 --- /dev/null +++ b/tests/rspack-test/normalCases/esm/reexport-wave-parallel/right.js @@ -0,0 +1,2 @@ +export { rightValue } from "./leaf-right"; +export const rightValueUsed = __webpack_exports_info__.rightValue.used; diff --git a/tests/rspack-test/normalCases/esm/reexport-wave-parallel/root.js b/tests/rspack-test/normalCases/esm/reexport-wave-parallel/root.js new file mode 100644 index 000000000000..fc3626c90525 --- /dev/null +++ b/tests/rspack-test/normalCases/esm/reexport-wave-parallel/root.js @@ -0,0 +1,7 @@ +export { leftValue } from "./left"; +export { rightValue } from "./right"; + +export const meta = { + leftValueUsed: __webpack_exports_info__.leftValue.used, + rightValueUsed: __webpack_exports_info__.rightValue.used, +}; diff --git a/tests/rspack-test/normalCases/parsing/commonjs-export-require-property-star/bridge.js b/tests/rspack-test/normalCases/parsing/commonjs-export-require-property-star/bridge.js new file mode 100644 index 000000000000..50d569219a0b --- /dev/null +++ b/tests/rspack-test/normalCases/parsing/commonjs-export-require-property-star/bridge.js @@ -0,0 +1 @@ +module.exports = require("./data.json").foo; diff --git a/tests/rspack-test/normalCases/parsing/commonjs-export-require-property-star/data.json b/tests/rspack-test/normalCases/parsing/commonjs-export-require-property-star/data.json new file mode 100644 index 000000000000..d9239bb56b6f --- /dev/null +++ b/tests/rspack-test/normalCases/parsing/commonjs-export-require-property-star/data.json @@ -0,0 +1,6 @@ +{ + "foo": { + "a": "a", + "b": "b" + } +} diff --git a/tests/rspack-test/normalCases/parsing/commonjs-export-require-property-star/index.js b/tests/rspack-test/normalCases/parsing/commonjs-export-require-property-star/index.js new file mode 100644 index 000000000000..e972b8d1d021 --- /dev/null +++ b/tests/rspack-test/normalCases/parsing/commonjs-export-require-property-star/index.js @@ -0,0 +1,10 @@ +it("should keep nested commonjs require reexports stable", function() { + const ns = require("./root"); + + expect(ns).toEqual( + nsObj({ + a: "a", + b: "b" + }) + ); +}); diff --git a/tests/rspack-test/normalCases/parsing/commonjs-export-require-property-star/root.js b/tests/rspack-test/normalCases/parsing/commonjs-export-require-property-star/root.js new file mode 100644 index 000000000000..a88679f56c31 --- /dev/null +++ b/tests/rspack-test/normalCases/parsing/commonjs-export-require-property-star/root.js @@ -0,0 +1 @@ +export * from "./bridge"; diff --git a/xtask/benchmark/benches/groups/compilation_stages.rs b/xtask/benchmark/benches/groups/compilation_stages.rs index d5bdfebd729a..ca832688e201 100644 --- a/xtask/benchmark/benches/groups/compilation_stages.rs +++ b/xtask/benchmark/benches/groups/compilation_stages.rs @@ -702,6 +702,8 @@ async fn prepare_for_concatenate_module(compiler: &mut Compiler) -> Result<()> { async fn run_finish_modules_hook(compilation: &mut Compilation) -> Result<()> { let mut async_modules_artifact = compilation.async_modules_artifact.steal(); let mut exports_info_artifact = compilation.exports_info_artifact.steal(); + let mut dependency_exports_analysis_artifact = + compilation.dependency_exports_analysis_artifact.steal(); compilation .plugin_driver .clone() @@ -711,10 +713,12 @@ async fn run_finish_modules_hook(compilation: &mut Compilation) -> Result<()> { compilation, &mut async_modules_artifact, &mut exports_info_artifact, + &mut dependency_exports_analysis_artifact, ) .await?; compilation.async_modules_artifact = async_modules_artifact.into(); compilation.exports_info_artifact = exports_info_artifact.into(); + compilation.dependency_exports_analysis_artifact = dependency_exports_analysis_artifact.into(); Ok(()) }