use std::{
hash::{Hash, Hasher},
ops::Deref,
sync::Arc,
};
use futures::{
StreamExt,
channel::{mpsc, oneshot},
future::join_all,
};
use rayon::prelude::*;
use rspack_core::{
ChunkByUkey, ChunkUkey, Compilation, ExportsInfoArtifact, Module, ModuleIdentifier,
RuntimeKeyMap, UsageKey, get_runtime_key,
};
use rspack_error::{Result, ToStringResultToRspackResultExt};
use rspack_util::{fx_hash::FxDashMap, tracing_preset::TRACING_BENCH_TARGET};
use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
use tracing::instrument;
use super::ModuleGroupMap;
use crate::{
SplitChunksNameBatchFn, SplitChunksPlugin,
common::{ChunkFilter, ModuleChunkMap, ModuleChunks, ModuleSizes},
min_size::remove_min_size_violating_modules,
module_group::{IndexedCacheGroup, ModuleGroup, ModuleGroupKey, compare_entries},
options::{
cache_group::CacheGroup,
cache_group_test::{CacheGroupTest, CacheGroupTestFnCtx},
chunk_name::{ChunkNameGetter, ChunkNameGetterFnCtx},
},
};
type ChunksKey = u64;
#[derive(Clone)]
struct ChunkCombination {
key: ChunksKey,
chunks: Arc<FxHashSet<ChunkUkey>>,
}
impl Deref for ChunkCombination {
type Target = FxHashSet<ChunkUkey>;
fn deref(&self) -> &Self::Target {
&self.chunks
}
}
enum SelectedChunks<'a> {
All(&'a ChunkCombination),
Filtered(Vec<ChunkUkey>),
}
enum SelectedChunksIter<'a> {
All(std::collections::hash_set::Iter<'a, ChunkUkey>),
Filtered(std::slice::Iter<'a, ChunkUkey>),
}
impl<'a> Iterator for SelectedChunksIter<'a> {
type Item = &'a ChunkUkey;
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::All(iter) => iter.next(),
Self::Filtered(iter) => iter.next(),
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self {
Self::All(iter) => iter.size_hint(),
Self::Filtered(iter) => iter.size_hint(),
}
}
}
impl SelectedChunks<'_> {
fn len(&self) -> usize {
match self {
Self::All(chunks) => chunks.len(),
Self::Filtered(chunks) => chunks.len(),
}
}
fn iter(&self) -> SelectedChunksIter<'_> {
match self {
Self::All(chunks) => SelectedChunksIter::All(chunks.iter()),
Self::Filtered(chunks) => SelectedChunksIter::Filtered(chunks.iter()),
}
}
fn key(&self) -> Option<ChunksKey> {
match self {
Self::All(chunks) => Some(chunks.key),
Self::Filtered(_) => None,
}
}
}
struct MatchedItem<'a> {
module: &'a dyn Module,
cache_group_index: u32,
cache_group: &'a CacheGroup,
selected_chunks: SelectedChunks<'a>,
}
struct PendingNameRequest {
module: ModuleIdentifier,
chunks: Vec<ChunkUkey>,
cache_group_position: usize,
response: Option<oneshot::Sender<Option<String>>>,
}
const JS_CHUNK_NAME_BATCH_SIZE: usize = 128;
async fn process_name_requests(
mut receiver: mpsc::UnboundedReceiver<PendingNameRequest>,
cache_groups: &[IndexedCacheGroup<'_>],
name_batch_getters: &[Option<SplitChunksNameBatchFn>],
compilation: &Compilation,
) -> Result<()> {
let module_graph = compilation.get_module_graph();
while let Some(first_request) = receiver.next().await {
let mut requests = Vec::with_capacity(JS_CHUNK_NAME_BATCH_SIZE);
requests.push(first_request);
while requests.len() < JS_CHUNK_NAME_BATCH_SIZE
&& let Ok(request) = receiver.try_recv()
{
requests.push(request);
}
let mut run_start = 0;
while run_start < requests.len() {
let cache_group_position = requests[run_start].cache_group_position;
let mut run_end = run_start + 1;
while run_end < requests.len()
&& requests[run_end].cache_group_position == cache_group_position
{
run_end += 1;
}
let indexed_cache_group = &cache_groups[cache_group_position];
let cache_group = indexed_cache_group.cache_group;
let get_name = name_batch_getters
.get(indexed_cache_group.cache_group_index as usize)
.and_then(Option::as_ref)
.expect("pending name request should use a batch name callback");
let contexts = requests[run_start..run_end]
.iter()
.map(|request| ChunkNameGetterFnCtx {
module: module_graph
.module_by_identifier(&request.module)
.expect("should have module")
.as_ref(),
compilation,
chunks: &request.chunks,
cache_group_key: &cache_group.key,
})
.collect();
let names = get_name(contexts).await?;
debug_assert_eq!(names.len(), run_end - run_start);
for (request, name) in requests[run_start..run_end].iter_mut().zip(names) {
if let Some(response) = request.response.take() {
let _ = response.send(name);
}
}
run_start = run_end;
}
}
Ok(())
}
fn get_key<I: Iterator<Item = ChunkUkey>>(
chunks: I,
chunk_index_map: &FxHashMap<ChunkUkey, u32>,
) -> ChunksKey {
let mut sorted_chunk_ukeys = chunks
.map(|chunk| {
*chunk_index_map
.get(&chunk)
.expect("should already have index for chunk ukey")
})
.collect::<Vec<_>>();
sorted_chunk_ukeys.sort_unstable();
let mut hasher = FxHasher::default();
for chunk_ukey in sorted_chunk_ukeys {
chunk_ukey.hash(&mut hasher);
}
hasher.finish()
}
#[derive(Default)]
pub(crate) struct Combinator {
combinations: FxHashMap<ChunksKey, Vec<ChunkCombination>>,
used_exports_combinations: FxHashMap<ChunksKey, Vec<ChunkCombination>>,
non_used_exports_chunks_keys: Vec<Option<ChunksKey>>,
grouped_by_exports: Vec<Vec<ChunksKey>>,
}
enum ChunkCombinations<'a> {
Slice(&'a [ChunkCombination]),
UsedExports(Vec<&'a ChunkCombination>),
}
enum ChunkCombinationsIter<'a> {
Slice(std::slice::Iter<'a, ChunkCombination>),
UsedExports(std::iter::Copied<std::slice::Iter<'a, &'a ChunkCombination>>),
}
impl<'a> Iterator for ChunkCombinationsIter<'a> {
type Item = &'a ChunkCombination;
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Slice(iter) => iter.next(),
Self::UsedExports(iter) => iter.next(),
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self {
Self::Slice(iter) => iter.size_hint(),
Self::UsedExports(iter) => iter.size_hint(),
}
}
}
impl<'a> IntoIterator for &'a ChunkCombinations<'a> {
type Item = &'a ChunkCombination;
type IntoIter = ChunkCombinationsIter<'a>;
fn into_iter(self) -> Self::IntoIter {
match self {
ChunkCombinations::Slice(combs) => ChunkCombinationsIter::Slice(combs.iter()),
ChunkCombinations::UsedExports(combs) => {
ChunkCombinationsIter::UsedExports(combs.iter().copied())
}
}
}
}
impl Combinator {
fn get_non_used_exports_combs(
&self,
module_index: usize,
module_chunks: &ModuleChunks,
chunk_index_map: &FxHashMap<ChunkUkey, u32>,
) -> &[ChunkCombination] {
let chunks = module_chunks
.get(module_index)
.expect("should have module chunks");
let chunks_key = self
.non_used_exports_chunks_keys
.get(module_index)
.and_then(|key| *key)
.unwrap_or_else(|| get_key(chunks.iter().copied(), chunk_index_map));
self
.combinations
.get(&chunks_key)
.expect("should have combinations")
}
fn get_used_exports_combs(&self, module_index: usize) -> Vec<&ChunkCombination> {
let mut result = vec![];
let chunks_by_module_used = self
.grouped_by_exports
.get(module_index)
.expect("should have exports for module");
for chunks_key in chunks_by_module_used.iter() {
let combs = self
.used_exports_combinations
.get(chunks_key)
.expect("should have combinations");
result.extend(combs.iter());
}
result
}
fn group_chunks_by_exports(
module_identifier: &ModuleIdentifier,
module_chunks: impl Iterator<Item = ChunkUkey>,
exports_info_artifact: &ExportsInfoArtifact,
chunk_by_ukey: &ChunkByUkey,
chunk_index_map: &FxHashMap<ChunkUkey, u32>,
) -> Vec<ChunkCombination> {
let exports_info = exports_info_artifact.get_exports_info_data(module_identifier);
let mut grouped_by_used_exports: FxHashMap<UsageKey, FxHashSet<ChunkUkey>> = Default::default();
let mut runtime_key_map = RuntimeKeyMap::default();
for chunk_ukey in module_chunks {
let chunk = chunk_by_ukey.expect_get(&chunk_ukey);
let runtime = chunk.runtime();
let usage_key = runtime_key_map
.entry(get_runtime_key(runtime).clone())
.or_insert_with(|| exports_info.get_usage_key(Some(runtime)))
.clone();
grouped_by_used_exports
.entry(usage_key)
.or_default()
.insert(chunk_ukey);
}
grouped_by_used_exports
.into_values()
.map(|chunks| ChunkCombination {
key: get_key(chunks.iter().copied(), chunk_index_map),
chunks: Arc::new(chunks),
})
.collect()
}
fn get_combs(
&self,
module_index: usize,
used_exports: bool,
module_chunks: &ModuleChunks,
chunk_index_map: &FxHashMap<ChunkUkey, u32>,
) -> ChunkCombinations<'_> {
if used_exports {
ChunkCombinations::UsedExports(self.get_used_exports_combs(module_index))
} else {
ChunkCombinations::Slice(self.get_non_used_exports_combs(
module_index,
module_chunks,
chunk_index_map,
))
}
}
fn get_combinations(
chunk_sets_in_graph: FxHashMap<ChunksKey, ChunkCombination>,
chunk_sets_by_count: Vec<ChunkCombination>,
) -> FxHashMap<ChunksKey, Vec<ChunkCombination>> {
chunk_sets_in_graph
.into_par_iter()
.map(|(chunks_key, chunks_set)| {
let mut result = vec![];
let chunks_set_len = chunks_set.len();
for set in &chunk_sets_by_count {
if set.len() >= chunks_set_len {
break;
}
if set.is_subset(&chunks_set) {
result.push(set.clone());
}
}
result.push(chunks_set);
(chunks_key, result)
})
.collect::<FxHashMap<_, _>>()
}
pub(crate) fn prepare_group_by_chunks(
&mut self,
all_modules: &[ModuleIdentifier],
module_chunks: &ModuleChunks,
chunk_index_map: &FxHashMap<ChunkUkey, u32>,
min_chunks: usize,
) {
self.non_used_exports_chunks_keys = all_modules
.par_iter()
.enumerate()
.map(|(module_index, _)| {
let chunks = module_chunks
.get(module_index)
.expect("should have module chunks");
if chunks.is_empty() || chunks.len() < min_chunks {
None
} else {
Some(get_key(chunks.iter().copied(), chunk_index_map))
}
})
.collect::<Vec<_>>();
let mut chunk_sets_in_graph = FxHashMap::with_capacity_and_hasher(
self.non_used_exports_chunks_keys.len(),
Default::default(),
);
for (module_index, chunk_key) in self
.non_used_exports_chunks_keys
.iter()
.enumerate()
.filter_map(|(module_index, chunk_key)| chunk_key.map(|chunk_key| (module_index, chunk_key)))
{
chunk_sets_in_graph
.entry(chunk_key)
.or_insert_with(|| ChunkCombination {
key: chunk_key,
chunks: Arc::new(
module_chunks
.get(module_index)
.expect("should have module chunks")
.clone(),
),
});
}
let mut chunk_sets_by_count = Vec::<ChunkCombination>::with_capacity(chunk_sets_in_graph.len());
for chunks in chunk_sets_in_graph.values() {
chunk_sets_by_count.push(chunks.clone());
}
chunk_sets_by_count.sort_unstable_by_key(|chunks| chunks.len());
self.combinations = Self::get_combinations(chunk_sets_in_graph, chunk_sets_by_count);
}
pub(crate) fn prepare_group_by_used_exports(
&mut self,
all_modules: &[ModuleIdentifier],
exports_info_artifact: &ExportsInfoArtifact,
chunk_by_ukey: &ChunkByUkey,
module_chunks: &ModuleChunks,
chunk_index_map: &FxHashMap<ChunkUkey, u32>,
) {
let (grouped_by_exports, used_exports_chunks): (Vec<_>, Vec<_>) = all_modules
.par_iter()
.enumerate()
.map(|(module_index, module)| {
let grouped_chunks = Self::group_chunks_by_exports(
module,
module_chunks
.get(module_index)
.expect("should have module chunks")
.iter()
.copied(),
exports_info_artifact,
chunk_by_ukey,
chunk_index_map,
);
let mut grouped_chunks_key = Vec::with_capacity(grouped_chunks.len());
let mut used_exports_chunks = Vec::with_capacity(grouped_chunks.len());
for chunks in grouped_chunks {
if chunks.is_empty() {
continue;
}
grouped_chunks_key.push(chunks.key);
used_exports_chunks.push(chunks);
}
(grouped_chunks_key, used_exports_chunks)
})
.unzip();
self.grouped_by_exports = grouped_by_exports;
let mut used_exports_chunk_sets_in_graph = FxHashMap::default();
let mut used_exports_chunk_sets_by_count = Vec::<ChunkCombination>::default();
for used_exports_chunks in used_exports_chunks {
for chunks in used_exports_chunks {
let chunk_key = chunks.key;
if let std::collections::hash_map::Entry::Vacant(entry) =
used_exports_chunk_sets_in_graph.entry(chunk_key)
{
used_exports_chunk_sets_by_count.push(chunks.clone());
entry.insert(chunks);
}
}
}
used_exports_chunk_sets_by_count.sort_unstable_by_key(|chunks| chunks.len());
self.used_exports_combinations = Self::get_combinations(
used_exports_chunk_sets_in_graph,
used_exports_chunk_sets_by_count,
);
}
}
impl SplitChunksPlugin {
pub(crate) fn find_best_module_group(
&self,
module_group_map: &mut ModuleGroupMap,
) -> (ModuleGroupKey, ModuleGroup) {
debug_assert!(!module_group_map.is_empty());
let mut best_entry_index = 0;
for entry_index in 1..module_group_map.len() {
let [(entry_key, entry), (best_entry_key, best_entry)] = module_group_map
.get_disjoint_indices_mut([entry_index, best_entry_index])
.expect("entry indices should be valid and unique");
let result = compare_entries((entry_key, entry), (best_entry_key, best_entry));
if result > 0f64 {
best_entry_index = entry_index;
}
}
module_group_map
.swap_remove_index(best_entry_index)
.expect("This should never happen, please file an issue")
}
#[allow(clippy::too_many_arguments)]
#[instrument(name = "Compilation:SplitChunks:prepare_module_group_map",target=TRACING_BENCH_TARGET, skip_all)]
pub(crate) async fn prepare_module_group_map(
&self,
combinator: &Combinator,
all_modules: &[ModuleIdentifier],
cache_groups: Vec<IndexedCacheGroup<'_>>,
compilation: &Compilation,
module_chunks: &ModuleChunks,
chunk_index_map: &FxHashMap<ChunkUkey, u32>,
) -> Result<ModuleGroupMap> {
let module_graph = compilation.get_module_graph();
let module_group_map: FxDashMap<ModuleGroupKey, ModuleGroup> = FxDashMap::default();
let name_batch_getters = self.name_batch_getters.as_deref();
let has_name_batch_callback = name_batch_getters.is_some_and(|name_batch_getters| {
cache_groups.iter().any(|indexed_cache_group| {
name_batch_getters
.get(indexed_cache_group.cache_group_index as usize)
.is_some_and(Option::is_some)
})
});
let (name_sender, name_receiver) = if has_name_batch_callback {
let (sender, receiver) = mpsc::unbounded();
(Some(sender), Some(receiver))
} else {
(None, None)
};
let module_group_results = rspack_parallel::scope::<_, Result<_>>(|token| {
if let Some(name_receiver) = name_receiver {
let coordinator = unsafe {
token.used((
name_receiver,
&cache_groups,
name_batch_getters.expect("should have batch name getters"),
compilation,
))
};
coordinator.spawn(
|(name_receiver, cache_groups, name_batch_getters, compilation)| async move {
process_name_requests(name_receiver, cache_groups, name_batch_getters, compilation)
.await
},
);
}
all_modules
.iter()
.enumerate()
.for_each(|(module_index, module_identifier)| {
let name_sender = name_sender.clone();
let s = unsafe {
token.used((
&cache_groups,
module_index,
module_identifier,
&module_graph,
compilation,
&module_group_map,
combinator,
module_chunks,
chunk_index_map,
name_sender,
name_batch_getters,
))
};
s.spawn(
|(
cache_groups,
module_index,
module_identifier,
module_graph,
compilation,
module_group_map,
combinator,
module_chunks,
chunk_index_map,
name_sender,
name_batch_getters,
)| async move {
let belong_to_chunks = module_chunks
.get(module_index)
.expect("should have module chunks");
if belong_to_chunks.is_empty() {
return Ok(());
}
let module = module_graph
.module_by_identifier(module_identifier)
.expect("should have module")
.as_ref();
let mut used_exports_combinations = None;
let mut non_used_exports_combinations = None;
for (cache_group_position, indexed_cache_group) in cache_groups.iter().enumerate() {
let cache_group = indexed_cache_group.cache_group;
let has_name_batch_getter = name_batch_getters.is_some_and(|getters| {
getters
.get(indexed_cache_group.cache_group_index as usize)
.is_some_and(Option::is_some)
});
if !(cache_group.r#type)(module)
|| !(cache_group.layer)(module.get_layer().map(ToString::to_string)).await?
{
continue;
}
let is_match = match &cache_group.test {
CacheGroupTest::String(test) => module
.name_for_condition()
.is_some_and(|name| name.starts_with(test)),
CacheGroupTest::RegExp(test) => module
.name_for_condition()
.is_some_and(|name| test.test(&name)),
CacheGroupTest::Fn(test) => test(CacheGroupTestFnCtx {
compilation,
module,
})
.await?
.unwrap_or_default(),
CacheGroupTest::Enabled => true,
};
if !is_match || belong_to_chunks.len() < cache_group.min_chunks as usize {
continue;
}
let combinations = if cache_group.used_exports {
if used_exports_combinations.is_none() {
used_exports_combinations = Some(combinator.get_combs(
module_index,
true,
module_chunks,
chunk_index_map,
));
}
used_exports_combinations
.as_ref()
.expect("should have used exports combinations")
} else {
if non_used_exports_combinations.is_none() {
non_used_exports_combinations = Some(combinator.get_combs(
module_index,
false,
module_chunks,
chunk_index_map,
));
}
non_used_exports_combinations
.as_ref()
.expect("should have non-used exports combinations")
};
for chunk_combination in combinations {
if chunk_combination.is_empty()
|| chunk_combination.len() < cache_group.min_chunks as usize
{
continue;
}
if matches!(&cache_group.chunk_filter, ChunkFilter::All)
&& matches!(&cache_group.name, ChunkNameGetter::Disabled)
&& !has_name_batch_getter
{
let mut module_group = module_group_map
.entry(ModuleGroupKey::Anonymous {
cache_group_index: indexed_cache_group.cache_group_index,
chunks_key: chunk_combination.key,
})
.or_insert_with(|| {
ModuleGroup::new(None, indexed_cache_group.cache_group_index, cache_group)
});
module_group.add_module_with_shared_chunks(
module.identifier(),
chunk_combination.iter().copied(),
);
continue;
}
let selected_chunks = match &cache_group.chunk_filter {
ChunkFilter::All => SelectedChunks::All(chunk_combination),
ChunkFilter::Func(_) => SelectedChunks::Filtered(
join_all(chunk_combination.iter().map(|chunk| async move {
cache_group
.chunk_filter
.test_func(chunk, compilation)
.await
.map(|matched| (chunk, matched))
}))
.await
.into_iter()
.collect::<Result<Vec<_>>>()?
.into_iter()
.filter_map(|(chunk, matched)| matched.then_some(*chunk))
.collect(),
),
_ => SelectedChunks::Filtered(
chunk_combination
.iter()
.filter(|chunk| cache_group.chunk_filter.test_internal(chunk, compilation))
.copied()
.collect(),
),
};
if selected_chunks.len() < cache_group.min_chunks as usize {
continue;
}
let chunk_name = if has_name_batch_getter {
let name_sender = name_sender
.as_ref()
.expect("name callback should have a batch coordinator");
let (response, response_receiver) = oneshot::channel();
if name_sender
.unbounded_send(PendingNameRequest {
module: module.identifier(),
chunks: selected_chunks.iter().copied().collect(),
cache_group_position,
response: Some(response),
})
.is_err()
{
return Ok(());
}
let Ok(chunk_name) = response_receiver.await else {
return Ok(());
};
chunk_name
} else {
match &cache_group.name {
ChunkNameGetter::String(name) => Some(name.clone()),
ChunkNameGetter::Disabled => None,
ChunkNameGetter::Fn(get_name) => {
let chunks = selected_chunks.iter().copied().collect::<Vec<_>>();
get_name(ChunkNameGetterFnCtx {
module,
compilation,
chunks: &chunks,
cache_group_key: &cache_group.key,
})
.await?
}
}
};
merge_matched_item_into_module_group_map(
MatchedItem {
module,
cache_group,
cache_group_index: indexed_cache_group.cache_group_index,
selected_chunks,
},
chunk_name,
module_group_map,
chunk_index_map,
);
}
}
Ok(())
},
);
});
drop(name_sender);
})
.await
.into_iter()
.map(|result| result.to_rspack_result())
.collect::<Result<Vec<_>>>()?;
for result in module_group_results {
result?;
}
let module_group_count = module_group_map.len();
let mut result = Vec::with_capacity(module_group_count);
result.extend(module_group_map);
result.sort_by(|a, b| a.0.cmp(&b.0));
let mut ordered_result =
ModuleGroupMap::with_capacity_and_hasher(module_group_count, Default::default());
ordered_result.extend(result);
Ok(ordered_result)
}
pub(crate) fn remove_all_modules_from_other_module_groups(
&self,
placed_module_chunks: &ModuleChunkMap,
module_group_map: &mut ModuleGroupMap,
module_sizes: &ModuleSizes,
) {
let keys_of_invalid_group = module_group_map
.par_iter_mut()
.filter_map(|(key, other_module_group)| {
let duplicated_modules = match (
placed_module_chunks,
other_module_group.shared_module_chunks(),
) {
(
ModuleChunkMap::Shared {
modules,
chunks: placed_chunks,
},
Some(other_chunks),
) => {
other_chunks.intersection(placed_chunks).next()?;
if other_module_group.modules.len() > modules.len() {
modules
.intersection(&other_module_group.modules)
.copied()
.collect::<Vec<_>>()
} else {
other_module_group
.modules
.intersection(modules)
.copied()
.collect::<Vec<_>>()
}
}
_ => other_module_group
.modules
.iter()
.filter(|module| {
let Some(placed_chunks) = placed_module_chunks.get(module) else {
return false;
};
let Some(other_chunks) = other_module_group.get_module_chunks(module) else {
return false;
};
placed_chunks.intersection(other_chunks).next().is_some()
})
.copied()
.collect::<Vec<_>>(),
};
if duplicated_modules.is_empty() {
return None;
}
other_module_group.remove_modules(duplicated_modules);
if other_module_group.modules.is_empty() {
tracing::trace!(
"{key} is deleted for having empty modules",
);
return Some(key.clone());
}
tracing::trace!("other_module_group: {other_module_group:#?}");
tracing::trace!("placed_module_chunks: {placed_module_chunks:#?}");
let cache_group = other_module_group.get_cache_group(&self.cache_groups);
if remove_min_size_violating_modules(key, other_module_group, cache_group, module_sizes) {
tracing::trace!(
"{key} is deleted for violating min_size {:#?}",
cache_group.min_size,
);
return Some(key.clone());
}
other_module_group.rebuild_chunks();
if other_module_group.chunks.len() < cache_group.min_chunks as usize {
tracing::trace!(
"{key} is deleted for each_module_group.chunks.len()({:?}) < cache_group.min_chunks({:?})",
other_module_group.chunks.len(),
cache_group.min_chunks
);
return Some(key.clone());
}
let chunks_len = other_module_group.chunks.len();
if !Self::check_min_size_reduction(
other_module_group.get_sizes(module_sizes),
&cache_group.min_size_reduction,
chunks_len,
) {
tracing::trace!(
"{key} is deleted for violating min_size {:#?}",
cache_group.min_size,
);
return Some(key.clone());
}
None
})
.collect::<Vec<_>>();
keys_of_invalid_group.into_iter().for_each(|key| {
module_group_map.swap_remove(&key);
});
}
}
fn merge_matched_item_into_module_group_map(
matched_item: MatchedItem<'_>,
chunk_name: Option<String>,
module_group_map: &FxDashMap<ModuleGroupKey, ModuleGroup>,
chunk_index_map: &FxHashMap<ChunkUkey, u32>,
) {
let MatchedItem {
module,
cache_group_index,
cache_group,
selected_chunks,
} = matched_item;
let is_named = chunk_name.is_some();
let key = if let Some(cache_group_name) = &chunk_name {
ModuleGroupKey::Named {
cache_group_index,
chunk_name: cache_group_name.clone(),
}
} else {
ModuleGroupKey::Anonymous {
cache_group_index,
chunks_key: selected_chunks
.key()
.unwrap_or_else(|| get_key(selected_chunks.iter().copied(), chunk_index_map)),
}
};
let mut module_group = {
module_group_map
.entry(key)
.or_insert_with(|| ModuleGroup::new(chunk_name, cache_group_index, cache_group))
};
merge_matched_item_into_module_group(module, selected_chunks, is_named, &mut module_group);
}
fn merge_matched_item_into_module_group(
module: &dyn Module,
selected_chunks: SelectedChunks<'_>,
is_named: bool,
module_group: &mut ModuleGroup,
) {
if is_named {
module_group.add_module(module.identifier(), selected_chunks.iter().copied());
} else {
module_group
.add_module_with_shared_chunks(module.identifier(), selected_chunks.iter().copied());
}
}