diff --git a/Cargo.lock b/Cargo.lock index 26c0ff3..cfb8e97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,6 +278,7 @@ dependencies = [ "async-trait", "better_scoped_tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "derivative", + "futures-util", "glob", "loader_compilation", "napi", @@ -287,6 +288,7 @@ dependencies = [ "rspack_binding_options", "rspack_core", "rspack_error", + "rspack_hash", "rspack_identifier", "rspack_ids", "rspack_loader_react_refresh", diff --git a/crates/binding_options/Cargo.toml b/crates/binding_options/Cargo.toml index 6f13b5a..2a21109 100644 --- a/crates/binding_options/Cargo.toml +++ b/crates/binding_options/Cargo.toml @@ -41,10 +41,12 @@ rspack_plugin_swc_js_minimizer = { path = "../.rspack_crates/rspack_plu rspack_plugin_wasm = { path = "../.rspack_crates/rspack_plugin_wasm" } rspack_plugin_worker = { path = "../.rspack_crates/rspack_plugin_worker" } rspack_regex = { path = "../.rspack_crates/rspack_regex" } +rspack_hash = { path = "../.rspack_crates/rspack_hash" } rspack_swc_visitors = { path = "../.rspack_crates/rspack_swc_visitors" } loader_compilation = { path = "../loader_compilation" } -plugin_manifest = { path = "../plugin_manifest" } +plugin_manifest = { path = "../plugin_manifest" } +futures-util = { workspace = true } anyhow = { workspace = true, features = ["backtrace"] } async-trait = { workspace = true } better_scoped_tls = { workspace = true } diff --git a/crates/binding_options/src/options/mod.rs b/crates/binding_options/src/options/mod.rs index 7716e4a..b5db707 100644 --- a/crates/binding_options/src/options/mod.rs +++ b/crates/binding_options/src/options/mod.rs @@ -83,7 +83,7 @@ impl RawOptionsApply for RSPackRawOptions { self.features.split_chunks_strategy.unwrap(), self.optimization, ); - optimization = split_chunk_strategy.apply(plugins)?; + optimization = split_chunk_strategy.apply(plugins, context.to_string())?; } else { optimization = IS_ENABLE_NEW_SPLIT_CHUNKS.set(&experiments.new_split_chunks, || { self.optimization.apply(plugins) diff --git a/crates/binding_options/src/options/raw_features.rs b/crates/binding_options/src/options/raw_features.rs index f6c76de..ceec742 100644 --- a/crates/binding_options/src/options/raw_features.rs +++ b/crates/binding_options/src/options/raw_features.rs @@ -1,6 +1,11 @@ +use std::{sync::Arc, hash::Hasher}; +use futures_util::{FutureExt, future}; use napi_derive::napi; use serde::Deserialize; -use rspack_core::{Optimization, PluginExt, SideEffectOption, UsedExportsOption, SourceType, BoxPlugin}; +use rspack_core::{ + Optimization, PluginExt, SideEffectOption, UsedExportsOption, SourceType, + BoxPlugin, Module, ModuleType, +}; use rspack_error::internal_error; use rspack_ids::{ DeterministicChunkIdsPlugin, DeterministicModuleIdsPlugin, NamedChunkIdsPlugin, @@ -9,9 +14,10 @@ use rspack_ids::{ use rspack_binding_options::RawOptimizationOptions; use rspack_plugin_split_chunks_new::{PluginOptions, CacheGroup}; use rspack_regex::RspackRegex; +use rspack_hash::{RspackHash, HashFunction, HashDigest}; pub struct SplitChunksStrategy { - strategy: String, + strategy: RawStrategyOptions, // Get the neccessary options from RawOptimizationOptions. chunk_ids: String, module_ids: String, @@ -23,34 +29,83 @@ pub struct SplitChunksStrategy { remove_available_modules: bool, } -fn get_plugin_options(strategy: String) -> rspack_plugin_split_chunks_new::PluginOptions { +fn get_modules_size(module: &dyn Module) -> f64 { + let mut size = 0f64; + for source_type in module.source_types() { + size += module.size(source_type); + } + size +} + +fn get_plugin_options(strategy: RawStrategyOptions, context: String) -> rspack_plugin_split_chunks_new::PluginOptions { use rspack_plugin_split_chunks_new::SplitChunkSizes; - tracing::info!("strategy: {}", strategy); let default_size_types = [SourceType::JavaScript, SourceType::Unknown]; let create_sizes = |size: Option| { size .map(|size| SplitChunkSizes::with_initial_value(&default_size_types, size)) .unwrap_or_else(SplitChunkSizes::default) }; + + let re_node_modules = RspackRegex::new("node_modules").unwrap(); let cache_groups = vec![ CacheGroup { key: String::from("framework"), name: rspack_plugin_split_chunks_new::create_chunk_name_getter_by_const_name("framework".to_string()), - chunk_filter: rspack_plugin_split_chunks_new::create_async_chunk_filter(), + chunk_filter: rspack_plugin_split_chunks_new::create_all_chunk_filter(), priority: 40.0, - test: rspack_plugin_split_chunks_new::create_module_filter_from_rspack_regex(RspackRegex::new("react|react-dom").unwrap()), + test: Arc::new(move |module: &dyn Module| -> bool { + module + .name_for_condition() + .map_or(false, |name| { + strategy.top_level_frameworks.iter().any(|framework| name.starts_with(framework)) + }) + }), max_initial_requests: 25, + max_async_requests: 25, reuse_existing_chunk: true, min_chunks: 1, - max_async_requests: 25, // When enfoce is true, all size should be set to SplitChunkSizes::empty(). + min_size: SplitChunkSizes::empty(), + max_async_size: SplitChunkSizes::empty(), + max_initial_size: SplitChunkSizes::empty(), + id_hint: String::from("framework"), + r#type: rspack_plugin_split_chunks_new::create_default_module_type_filter(), + }, + CacheGroup { + key: String::from("lib"), + name: Arc::new(move |module| { + let mut hash = RspackHash::new(&HashFunction::Xxhash64); + match module.module_type() { + ModuleType::Css | ModuleType::CssModule | ModuleType::CssAuto => { + module.update_hash(&mut hash); + }, + _ => { + let options = rspack_core::LibIdentOptions { context: &context }; + let lib_ident = module.lib_ident(options); + hash.write(lib_ident.unwrap().as_bytes()); + }, + } + future::ready(Some(hash.digest(&HashDigest::Hex).rendered(8).to_string())).boxed() + }), + chunk_filter: rspack_plugin_split_chunks_new::create_all_chunk_filter(), + test: Arc::new(move |module: &dyn Module| -> bool { + module + .name_for_condition() + .map_or(false, |name| re_node_modules.test(&name)) + && get_modules_size(module) > 160000.0 + }), + priority: 30.0, + min_chunks: 1, + reuse_existing_chunk: true, + max_initial_requests: 25, + max_async_requests: 25, min_size: create_sizes(Some(20000.0)), max_async_size: SplitChunkSizes::default(), max_initial_size: SplitChunkSizes::default(), - id_hint: String::from("framework"), + id_hint: String::from("lib"), r#type: rspack_plugin_split_chunks_new::create_default_module_type_filter(), - } + }, ]; PluginOptions { @@ -67,11 +122,11 @@ fn get_plugin_options(strategy: String) -> rspack_plugin_split_chunks_new::Plugi pub trait FeatureApply { type Options; - fn apply(self, plugins: &mut Vec) -> Result; + fn apply(self, plugins: &mut Vec, context: String) -> Result; } impl SplitChunksStrategy { - pub fn new(strategy: String, option: RawOptimizationOptions) -> Self { + pub fn new(strategy: RawStrategyOptions, option: RawOptimizationOptions) -> Self { Self { strategy, chunk_ids: option.chunk_ids, @@ -89,9 +144,9 @@ impl SplitChunksStrategy { impl FeatureApply for SplitChunksStrategy { type Options = Optimization; - fn apply(self, plugins: &mut Vec>) -> Result { + fn apply(self, plugins: &mut Vec>, context: String) -> Result { let split_chunks_plugin = rspack_plugin_split_chunks_new::SplitChunksPlugin::new( - get_plugin_options(self.strategy), + get_plugin_options(self.strategy, context), ).boxed(); plugins.push(split_chunks_plugin); @@ -128,9 +183,17 @@ impl FeatureApply for SplitChunksStrategy { } } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +#[napi(object)] +pub struct RawStrategyOptions { + pub name: String, + pub top_level_frameworks: Vec, +} + #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] #[napi(object)] pub struct RawFeatures { - pub split_chunks_strategy: Option, + pub split_chunks_strategy: Option, }