Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 6 additions & 22 deletions crates/rspack_binding_api/src/raw_options/raw_split_chunks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,38 +301,22 @@ fn create_module_type_filter(
raw: Either<RspackRegex, JsString>,
) -> rspack_plugin_split_chunks::ModuleTypeFilter {
match raw {
Either::A(regex) => Arc::new(move |m| regex.test(m.module_type().as_str())),
Either::B(js_str) => {
let type_str = js_str.into_string();
Arc::new(move |m| m.module_type().as_str() == type_str.as_str())
}
Either::A(regex) => rspack_plugin_split_chunks::ModuleTypeFilter::Regex(regex),
Either::B(js_str) => rspack_plugin_split_chunks::ModuleTypeFilter::String(js_str.into_string()),
}
}

fn create_module_layer_filter(
raw: Either3<RspackRegex, JsString, ThreadsafeFunction<Option<String>, bool>>,
) -> rspack_plugin_split_chunks::ModuleLayerFilter {
match raw {
Either3::A(regex) => Arc::new(move |layer| {
let regex = regex.clone();
Box::pin(async move { Ok(layer.map(|layer| regex.test(&layer)).unwrap_or_default()) })
}),
Either3::A(regex) => rspack_plugin_split_chunks::ModuleLayerFilter::Regex(regex),
Either3::B(js_str) => {
let test = js_str.into_string();
Arc::new(move |layer| {
let test = test.clone();
Box::pin(async move {
Ok(if let Some(layer) = layer {
layer.starts_with(&test)
} else {
test.is_empty()
})
})
})
rspack_plugin_split_chunks::ModuleLayerFilter::String(js_str.into_string())
}
Either3::C(f) => Arc::new(move |layer| {
Either3::C(f) => rspack_plugin_split_chunks::ModuleLayerFilter::Func(Arc::new(move |layer| {
let f = f.clone();
Box::pin(async move { f.call_with_sync(layer).await })
}),
})),
}
}
24 changes: 18 additions & 6 deletions crates/rspack_plugin_esm_library/src/split_chunks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,16 +174,28 @@ async fn matches_module_to_cache_group(
}

// match r#type
if !(cache_group.r#type)(module) {
if !cache_group
.r#type
.test_internal(module.module_type().as_str())
{
return Ok(false);
}

// match layer
if !(cache_group.layer)(module.get_layer().map(ToString::to_string))
.await
.to_rspack_result()
.unwrap_or(false)
{
let layer = module.get_layer();
let satisfied_layer = if cache_group.layer.is_func() {
cache_group
.layer
.test_func(layer.cloned())
.await
.to_rspack_result()
.unwrap_or(false)
} else {
cache_group
.layer
.test_internal(layer.map(|layer| layer.as_str()))
};
if !satisfied_layer {
return Ok(false);
}

Expand Down
63 changes: 58 additions & 5 deletions crates/rspack_plugin_split_chunks/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use derive_more::Debug;
use futures::future::BoxFuture;
use rayon::prelude::*;
use rspack_collections::IdentifierMap;
use rspack_core::{ChunkUkey, Compilation, Module, ModuleIdentifier, SourceType};
use rspack_core::{ChunkUkey, Compilation, ModuleIdentifier, SourceType};
use rspack_error::Result;
use rspack_regex::RspackRegex;
use rustc_hash::{FxHashMap, FxHashSet};
Expand Down Expand Up @@ -66,16 +66,69 @@ impl ChunkFilter {
}
}

pub type ModuleTypeFilter = Arc<dyn Fn(&dyn Module) -> bool + Send + Sync>;
pub type ModuleLayerFilter =
#[derive(Clone)]
pub enum ModuleTypeFilter {
All,
Regex(RspackRegex),
String(String),
}

impl ModuleTypeFilter {
pub fn test_internal(&self, module_type: &str) -> bool {
match self {
Self::All => true,
Self::Regex(re) => re.test(module_type),
Self::String(expected) => expected == module_type,
}
}
}

pub type ModuleLayerFilterFn =
Arc<dyn Fn(Option<String>) -> BoxFuture<'static, Result<bool>> + Send + Sync>;

#[derive(Clone)]
pub enum ModuleLayerFilter {
Func(ModuleLayerFilterFn),
All,
Regex(RspackRegex),
String(String),
}

impl ModuleLayerFilter {
pub fn is_func(&self) -> bool {
matches!(self, Self::Func(_))
}

pub async fn test_func(&self, layer: Option<String>) -> Result<bool> {
if let Self::Func(func) = self {
func(layer).await
} else {
panic!("ModuleLayerFilter is not a function");
}
}

pub fn test_internal(&self, layer: Option<&str>) -> bool {
match self {
Self::Func(_) => panic!("ModuleLayerFilter is a function"),
Self::All => true,
Self::Regex(re) => layer.is_some_and(|layer| re.test(layer)),
Self::String(test) => {
if let Some(layer) = layer {
layer.starts_with(test)
} else {
test.is_empty()
}
}
}
}
}

pub fn create_default_module_type_filter() -> ModuleTypeFilter {
Arc::new(|_| true)
ModuleTypeFilter::All
}

pub fn create_default_module_layer_filter() -> ModuleLayerFilter {
Arc::new(|_| Box::pin(async move { Ok(true) }))
ModuleLayerFilter::All
}

pub fn create_async_chunk_filter() -> ChunkFilter {
Expand Down
1 change: 1 addition & 0 deletions crates/rspack_plugin_split_chunks/src/module_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::{
common::{ModuleSizes, SplitChunkSizes},
};

#[derive(Clone, Copy)]
pub(crate) struct IndexedCacheGroup<'a> {
pub cache_group_index: u32,
pub cache_group: &'a CacheGroup,
Expand Down
160 changes: 139 additions & 21 deletions crates/rspack_plugin_split_chunks/src/plugin/module_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use tracing::instrument;
use super::ModuleGroupMap;
use crate::{
SplitChunksPlugin,
common::{ChunkFilter, ModuleChunks, ModuleSizes},
common::{ChunkFilter, ModuleChunks, ModuleSizes, ModuleTypeFilter},
min_size::remove_min_size_violating_modules,
module_group::{IndexedCacheGroup, ModuleGroup, ModuleGroupKey, compare_entries},
options::{
Expand All @@ -34,6 +34,57 @@ use crate::{
};

type ChunksKey = u64;
type ChunkFilterCacheKey = (u32, ChunksKey);

struct TypeFilteredCacheGroups<'a> {
all: Vec<IndexedCacheGroup<'a>>,
regex: Vec<IndexedCacheGroup<'a>>,
exact: FxHashMap<String, Vec<IndexedCacheGroup<'a>>>,
}

impl<'a> TypeFilteredCacheGroups<'a> {
fn new(cache_groups: &[IndexedCacheGroup<'a>]) -> Self {
let mut all = Vec::new();
let mut regex = Vec::new();
let mut exact = FxHashMap::default();

for cache_group in cache_groups.iter().copied() {
match &cache_group.cache_group.r#type {
ModuleTypeFilter::All => all.push(cache_group),
ModuleTypeFilter::Regex(_) => regex.push(cache_group),
ModuleTypeFilter::String(module_type) => exact
.entry(module_type.clone())
.or_insert_with(Vec::new)
.push(cache_group),
}
}

Self { all, regex, exact }
}

fn candidates(&self, module_type: &str) -> Vec<IndexedCacheGroup<'a>> {
let mut candidates = Vec::with_capacity(
self.all.len() + self.regex.len() + self.exact.get(module_type).map_or(0, Vec::len),
);

candidates.extend(self.all.iter().copied());

if let Some(exact) = self.exact.get(module_type) {
candidates.extend(exact.iter().copied());
}

candidates.extend(
self
.regex
.iter()
.copied()
.filter(|cache_group| cache_group.cache_group.r#type.test_internal(module_type)),
);

candidates.sort_unstable_by(|a, b| a.compare_by_index(b));
candidates
}
}

#[derive(Clone)]
struct ChunkCombination {
Expand Down Expand Up @@ -349,45 +400,99 @@ impl SplitChunksPlugin {
) -> Result<ModuleGroupMap> {
let module_graph = compilation.get_module_graph();
let module_group_map: FxDashMap<ModuleGroupKey, ModuleGroup> = FxDashMap::default();
let type_filtered_cache_groups = TypeFilteredCacheGroups::new(&cache_groups);
let sync_chunk_filter_result_cache: FxDashMap<ChunkFilterCacheKey, Arc<Vec<ChunkUkey>>> =
FxDashMap::default();
let module_group_results = rspack_parallel::scope::<_, Result<_>>(|token| {
all_modules.iter().for_each(|mid| {
let s = unsafe { token.used((&cache_groups, mid, &module_graph, compilation, &module_group_map, &combinator, module_chunks, removed_module_chunks, chunk_index_map)) };
s.spawn(|(cache_groups, mid, module_graph, compilation, module_group_map, combinator, module_chunks, removed_module_chunks, chunk_index_map)| async move {
let s = unsafe { token.used((&type_filtered_cache_groups, &sync_chunk_filter_result_cache, mid, &module_graph, compilation, &module_group_map, &combinator, module_chunks, removed_module_chunks, chunk_index_map)) };
s.spawn(|(type_filtered_cache_groups, sync_chunk_filter_result_cache, mid, module_graph, compilation, module_group_map, combinator, module_chunks, removed_module_chunks, chunk_index_map)| async move {
let belong_to_chunks = module_chunks.get(mid).expect("should have module chunks");
if belong_to_chunks.is_empty() {
return Ok(());
}

if let Some(removed_chunks) = removed_module_chunks.get(mid) && belong_to_chunks.iter().all(|c| removed_chunks.contains(c)) {
let removed_chunks = removed_module_chunks.get(mid);
let available_chunks_upper_bound = removed_chunks.map_or(belong_to_chunks.len(), |removed| {
belong_to_chunks.len() - belong_to_chunks.intersection(removed).count()
});

if available_chunks_upper_bound == 0 {
return Ok(());
}

let module = module_graph.module_by_identifier(mid).expect("should have module").as_ref();
let module_type = module.module_type().as_str();
let layer = module.get_layer().map(|layer| layer.as_str());
let mut name_for_condition: Option<Option<Box<str>>> = None;
let mut sync_chunk_filter_upper_bounds: FxHashMap<u32, usize> = FxHashMap::default();
let mut filtered = vec![];

for cache_group in cache_groups.iter() {
let mut is_match = true;
// Filter by `splitChunks.cacheGroups.{cacheGroup}.type`
is_match &= (cache_group.cache_group.r#type)(module);
for cache_group in type_filtered_cache_groups.candidates(module_type) {
if available_chunks_upper_bound < cache_group.cache_group.min_chunks as usize {
continue;
}

if !cache_group.cache_group.chunk_filter.is_func() {
let selected_chunks_upper_bound = *sync_chunk_filter_upper_bounds
.entry(cache_group.cache_group_index)
.or_insert_with(|| match &cache_group.cache_group.chunk_filter {
ChunkFilter::All => available_chunks_upper_bound,
chunk_filter => belong_to_chunks
.iter()
.filter(|chunk| {
!removed_chunks.is_some_and(|chunks| chunks.contains(chunk))
&& chunk_filter.test_internal(chunk, compilation)
})
.count(),
});

if selected_chunks_upper_bound < cache_group.cache_group.min_chunks as usize {
continue;
}
}

// Filter by `splitChunks.cacheGroups.{cacheGroup}.layer`
is_match &= (cache_group.cache_group.layer)(module.get_layer().map(ToString::to_string)).await?;
let is_match = if cache_group.cache_group.layer.is_func() {
cache_group
.cache_group
.layer
.test_func(layer.map(str::to_owned))
.await?
} else {
cache_group
.cache_group
.layer
.test_internal(layer)
};
if !is_match {
continue;
}

// Filter by `splitChunks.cacheGroups.{cacheGroup}.test`
is_match &= match &cache_group.cache_group.test {
CacheGroupTest::String(str) => module
.name_for_condition().is_some_and(|name| name.starts_with(str)),
CacheGroupTest::RegExp(regexp) => module
.name_for_condition().is_some_and(|name| regexp.test(&name)),
let is_match = match &cache_group.cache_group.test {
CacheGroupTest::String(str) => name_for_condition
.get_or_insert_with(|| module.name_for_condition())
.as_deref()
.is_some_and(|name| name.starts_with(str)),
CacheGroupTest::RegExp(regexp) => name_for_condition
.get_or_insert_with(|| module.name_for_condition())
.as_deref()
.is_some_and(|name| regexp.test(name)),
CacheGroupTest::Fn(f) => {
let ctx = CacheGroupTestFnCtx { compilation, module };
f(ctx).await?.unwrap_or_default()
}
CacheGroupTest::Enabled => true,
};

if is_match {
filtered.push(cache_group);
if !is_match {
continue;
}

filtered.push(cache_group);
}

let mut used_exports_combs = None;
let mut non_used_exports_combs = None;

Expand Down Expand Up @@ -455,9 +560,19 @@ impl SplitChunksPlugin {
}
).copied().collect::<Vec<_>>()
} else {
chunk_combination.iter().filter(|c| {
cache_group.chunk_filter.test_internal(c, compilation)
}).copied().collect::<Vec<_>>()
let cache_key = (cache_group_index, chunk_combination.key);
if let Some(selected_chunks) = sync_chunk_filter_result_cache.get(&cache_key) {
selected_chunks.value().as_ref().clone()
} else {
let selected_chunks = chunk_combination.iter().filter(|c| {
cache_group.chunk_filter.test_internal(c, compilation)
}).copied().collect::<Vec<_>>();
sync_chunk_filter_result_cache.insert(
cache_key,
Arc::new(selected_chunks.clone()),
);
selected_chunks
}
};

// Filter by `splitChunks.cacheGroups.{cacheGroup}.minChunks`
Expand All @@ -472,7 +587,10 @@ impl SplitChunksPlugin {
continue;
}

if selected_chunks.iter().any(|c| removed_module_chunks.get(mid).is_some_and(|chunks| chunks.contains(c))) {
if selected_chunks
.iter()
.any(|c| removed_chunks.is_some_and(|chunks| chunks.contains(c)))
{
continue;
}
let selected_chunks_key = matches!(&cache_group.chunk_filter, ChunkFilter::All)
Expand All @@ -481,7 +599,7 @@ impl SplitChunksPlugin {
MatchedItem {
module,
cache_group,
cache_group_index: *cache_group_index,
cache_group_index,
selected_chunks,
selected_chunks_key,
},
Expand Down
Loading
Loading