Skip to content

Commit 1e6e9a9

Browse files
authored
perf: improve bundle splitting (#11364)
* perf: improve bundle splitting * perf: improve bundle splitting * perf: improve bundle splitting
1 parent 72a544f commit 1e6e9a9

File tree

7 files changed

+242
-166
lines changed

7 files changed

+242
-166
lines changed

crates/rspack_binding_api/src/raw_options/raw_split_chunks/raw_split_chunk_chunks.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,26 @@ use std::sync::Arc;
22

33
use napi::{JsString, bindgen_prelude::Either3};
44
use rspack_napi::{string::JsStringExt, threadsafe_function::ThreadsafeFunction};
5+
use rspack_plugin_split_chunks::{
6+
ChunkFilter, create_chunk_filter_from_str, create_regex_chunk_filter_from_str,
7+
};
58
use rspack_regex::RspackRegex;
69

710
use crate::chunk::ChunkWrapper;
811

912
pub type Chunks<'a> = Either3<RspackRegex, JsString<'a>, ThreadsafeFunction<ChunkWrapper, bool>>;
1013

11-
pub fn create_chunks_filter(raw: Chunks) -> rspack_plugin_split_chunks::ChunkFilter {
14+
pub fn create_chunks_filter(raw: Chunks) -> ChunkFilter {
1215
match raw {
13-
Either3::A(regex) => rspack_plugin_split_chunks::create_regex_chunk_filter_from_str(regex),
16+
Either3::A(regex) => create_regex_chunk_filter_from_str(regex),
1417
Either3::B(js_str) => {
1518
let js_str = js_str.into_string();
16-
rspack_plugin_split_chunks::create_chunk_filter_from_str(&js_str)
19+
create_chunk_filter_from_str(&js_str)
1720
}
18-
Either3::C(f) => Arc::new(move |chunk_ukey, compilation| {
21+
Either3::C(f) => ChunkFilter::Func(Arc::new(move |chunk_ukey, compilation| {
1922
let f = f.clone();
2023
let chunk_wrapper = ChunkWrapper::new(*chunk_ukey, compilation);
2124
Box::pin(async move { f.call_with_sync(chunk_wrapper).await })
22-
}),
25+
})),
2326
}
2427
}

crates/rspack_core/src/chunk_graph/chunk_graph_chunk.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,39 @@ impl ChunkGraph {
353353
}
354354
}
355355

356+
pub fn disconnect_chunks_and_modules(
357+
&mut self,
358+
chunks: &[ChunkUkey],
359+
modules: &[ModuleIdentifier],
360+
) {
361+
for chunk in chunks.iter() {
362+
let cgc = self.expect_chunk_graph_chunk_mut(*chunk);
363+
for module in modules.iter() {
364+
cgc.modules.remove(module);
365+
if let Some(source_types_by_module) = &mut cgc.source_types_by_module {
366+
source_types_by_module.remove(module);
367+
}
368+
}
369+
}
370+
for module in modules.iter() {
371+
let cgm = self.expect_chunk_graph_module_mut(*module);
372+
for chunk in chunks.iter() {
373+
cgm.chunks.remove(chunk);
374+
}
375+
}
376+
}
377+
378+
pub fn connect_chunk_and_modules(&mut self, chunk: ChunkUkey, modules: &[ModuleIdentifier]) {
379+
for module in modules.iter() {
380+
let cgm = self.expect_chunk_graph_module_mut(*module);
381+
cgm.chunks.insert(chunk);
382+
}
383+
let cgc = self.expect_chunk_graph_chunk_mut(chunk);
384+
for module in modules.iter() {
385+
cgc.modules.insert(*module);
386+
}
387+
}
388+
356389
pub fn get_chunk_modules<'module>(
357390
&self,
358391
chunk: &ChunkUkey,

crates/rspack_plugin_split_chunks/src/common.rs

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,57 @@ use std::{
55

66
use derive_more::Debug;
77
use futures::future::BoxFuture;
8-
use rspack_collections::IdentifierMap;
8+
use rspack_collections::{IdentifierMap, UkeySet};
99
use rspack_core::{ChunkUkey, Compilation, Module, SourceType};
1010
use rspack_error::Result;
1111
use rspack_regex::RspackRegex;
1212
use rustc_hash::{FxHashMap, FxHashSet};
1313

14-
pub type ChunkFilter =
14+
pub type ChunkFilterFunc =
1515
Arc<dyn Fn(&ChunkUkey, &Compilation) -> BoxFuture<'static, Result<bool>> + Sync + Send>;
16+
17+
#[derive(Clone)]
18+
pub enum ChunkFilter {
19+
Func(ChunkFilterFunc),
20+
All,
21+
Regex(RspackRegex),
22+
Async,
23+
Initial,
24+
}
25+
26+
impl ChunkFilter {
27+
pub fn is_func(&self) -> bool {
28+
matches!(self, ChunkFilter::Func(_))
29+
}
30+
31+
pub async fn test_func(&self, chunk_ukey: &ChunkUkey, compilation: &Compilation) -> Result<bool> {
32+
if let ChunkFilter::Func(func) = self {
33+
func(chunk_ukey, compilation).await
34+
} else {
35+
panic!("ChunkFilter is not a function");
36+
}
37+
}
38+
39+
pub fn test_internal(&self, chunk_ukey: &ChunkUkey, compilation: &Compilation) -> bool {
40+
match self {
41+
ChunkFilter::Func(_) => panic!("ChunkFilter is a function"),
42+
ChunkFilter::All => true,
43+
ChunkFilter::Regex(re) => {
44+
let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
45+
chunk.name().is_some_and(|name| re.test(name))
46+
}
47+
ChunkFilter::Async => {
48+
let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
49+
!chunk.can_be_initial(&compilation.chunk_group_by_ukey)
50+
}
51+
ChunkFilter::Initial => {
52+
let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
53+
chunk.can_be_initial(&compilation.chunk_group_by_ukey)
54+
}
55+
}
56+
}
57+
}
58+
1659
pub type ModuleTypeFilter = Arc<dyn Fn(&dyn Module) -> bool + Send + Sync>;
1760
pub type ModuleLayerFilter =
1861
Arc<dyn Fn(Option<String>) -> BoxFuture<'static, Result<bool>> + Send + Sync>;
@@ -26,23 +69,15 @@ pub fn create_default_module_layer_filter() -> ModuleLayerFilter {
2669
}
2770

2871
pub fn create_async_chunk_filter() -> ChunkFilter {
29-
Arc::new(|chunk_ukey, compilation| {
30-
let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
31-
let can_be_initial = chunk.can_be_initial(&compilation.chunk_group_by_ukey);
32-
Box::pin(async move { Ok(!can_be_initial) })
33-
})
72+
ChunkFilter::Async
3473
}
3574

3675
pub fn create_initial_chunk_filter() -> ChunkFilter {
37-
Arc::new(|chunk_ukey, compilation| {
38-
let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
39-
let can_be_initial = chunk.can_be_initial(&compilation.chunk_group_by_ukey);
40-
Box::pin(async move { Ok(can_be_initial) })
41-
})
76+
ChunkFilter::Initial
4277
}
4378

4479
pub fn create_all_chunk_filter() -> ChunkFilter {
45-
Arc::new(|_chunk, _compilation| Box::pin(async move { Ok(true) }))
80+
ChunkFilter::All
4681
}
4782

4883
pub fn create_chunk_filter_from_str(chunks: &str) -> ChunkFilter {
@@ -55,11 +90,7 @@ pub fn create_chunk_filter_from_str(chunks: &str) -> ChunkFilter {
5590
}
5691

5792
pub fn create_regex_chunk_filter_from_str(re: RspackRegex) -> ChunkFilter {
58-
Arc::new(move |chunk_ukey, compilation| {
59-
let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
60-
let res = chunk.name().is_some_and(|name| re.test(name));
61-
Box::pin(async move { Ok(res) })
62-
})
93+
ChunkFilter::Regex(re)
6394
}
6495

6596
#[derive(Debug, Default, Clone)]
@@ -171,3 +202,4 @@ pub struct FallbackCacheGroup {
171202
}
172203

173204
pub(crate) type ModuleSizes = IdentifierMap<FxHashMap<SourceType, f64>>;
205+
pub(crate) type ModuleChunks = IdentifierMap<UkeySet<ChunkUkey>>;

crates/rspack_plugin_split_chunks/src/plugin/chunk.rs

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use rspack_collections::{DatabaseItem, UkeySet};
2-
use rspack_core::{Chunk, ChunkUkey, Compilation, incremental::Mutation};
1+
use rayon::prelude::*;
2+
use rspack_collections::{DatabaseItem, IdentifierMap, UkeySet};
3+
use rspack_core::{Chunk, ChunkUkey, Compilation, ModuleIdentifier, incremental::Mutation};
34

4-
use crate::{SplitChunksPlugin, module_group::ModuleGroup};
5+
use crate::{SplitChunksPlugin, common::ModuleChunks, module_group::ModuleGroup};
56

67
fn put_split_chunk_reason(
78
chunk_reason: &mut Option<String>,
@@ -21,6 +22,16 @@ fn put_split_chunk_reason(
2122
}
2223

2324
impl SplitChunksPlugin {
25+
pub(crate) fn get_module_chunks(
26+
all_modules: &[ModuleIdentifier],
27+
compilation: &Compilation,
28+
) -> ModuleChunks {
29+
let chunk_graph = &compilation.chunk_graph;
30+
all_modules
31+
.par_iter()
32+
.map(|module| (*module, chunk_graph.get_module_chunks(*module).clone()))
33+
.collect::<IdentifierMap<_>>()
34+
}
2435
/// Affected by `splitChunks.cacheGroups.{cacheGroup}.reuseExistingChunk`
2536
///
2637
/// If there is a code splitting chunk that the contains the same modules as the current `ModuleGroup`,
@@ -164,29 +175,31 @@ impl SplitChunksPlugin {
164175
original_chunks: &UkeySet<ChunkUkey>,
165176
compilation: &mut Compilation,
166177
) {
167-
for module_identifier in &item.modules {
168-
if let Some(module) = compilation.module_by_identifier(module_identifier)
169-
&& module
170-
.chunk_condition(&new_chunk, compilation)
171-
.is_some_and(|condition| !condition)
172-
{
173-
continue;
174-
}
178+
let modules = item
179+
.modules
180+
.iter()
181+
.filter(|mid| {
182+
if let Some(module) = compilation.module_by_identifier(mid)
183+
&& module
184+
.chunk_condition(&new_chunk, compilation)
185+
.is_some_and(|condition| !condition)
186+
{
187+
return false;
188+
}
189+
true
190+
})
191+
.copied()
192+
.collect::<Vec<_>>();
175193

176-
// First, we remove modules from old chunks
194+
let chunks = original_chunks.iter().copied().collect::<Vec<_>>();
177195

178-
// Remove module from old chunks
179-
for used_chunk in original_chunks {
180-
compilation
181-
.chunk_graph
182-
.disconnect_chunk_and_module(used_chunk, *module_identifier);
183-
}
196+
compilation
197+
.chunk_graph
198+
.disconnect_chunks_and_modules(&chunks, &modules);
184199

185-
// Add module to new chunk
186-
compilation
187-
.chunk_graph
188-
.connect_chunk_and_module(new_chunk, *module_identifier);
189-
}
200+
compilation
201+
.chunk_graph
202+
.connect_chunk_and_modules(new_chunk, &modules);
190203
}
191204

192205
/// Since the modules are moved into the `new_chunk`, we should

crates/rspack_plugin_split_chunks/src/plugin/max_size.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,11 @@ impl SplitChunksPlugin {
515515
);
516516

517517
if max_size_setting.is_none()
518-
&& !(fallback_cache_group.chunks_filter)(&chunk.ukey(), compilation).await?
518+
&& !(if fallback_cache_group.chunks_filter.is_func() {
519+
fallback_cache_group.chunks_filter.test_func(&chunk.ukey(), compilation).await?
520+
} else {
521+
fallback_cache_group.chunks_filter.test_internal(&chunk.ukey(), compilation)
522+
})
519523
{
520524
tracing::debug!("Chunk({:?}) skips `maxSize` checking. Reason: max_size_setting.is_none() and chunks_filter is false", chunk.chunk_reason());
521525
return Ok(None);

crates/rspack_plugin_split_chunks/src/plugin/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ impl SplitChunksPlugin {
5454
.collect::<Vec<_>>();
5555

5656
let module_sizes = Self::get_module_sizes(&all_modules, compilation);
57+
let module_chunks = Self::get_module_chunks(&all_modules, compilation);
5758

5859
let mut module_group_map = self
59-
.prepare_module_group_map(&all_modules, compilation, &module_sizes)
60+
.prepare_module_group_map(&all_modules, compilation, &module_sizes, &module_chunks)
6061
.await?;
6162
tracing::trace!("prepared module_group_map {:#?}", module_group_map);
6263
logger.time_end(start);

0 commit comments

Comments
 (0)