diff --git a/Cargo.lock b/Cargo.lock index a2cd127e188..74d2ab473d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3383,6 +3383,7 @@ dependencies = [ "rspack_plugin_schemes", "rspack_plugin_size_limits", "rspack_plugin_split_chunks", + "rspack_plugin_split_shared_modules", "rspack_plugin_swc_js_minimizer", "rspack_plugin_warn_sensitive_module", "rspack_plugin_wasm", @@ -4298,6 +4299,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "rspack_plugin_split_shared_modules" +version = "0.1.0" +dependencies = [ + "dashmap 5.5.3", + "derivative", + "rayon", + "regex", + "rspack_collections", + "rspack_core", + "rspack_error", + "rspack_hash", + "rspack_hook", + "rspack_regex", + "rspack_util", + "rustc-hash 1.1.0", + "tracing", +] + [[package]] name = "rspack_plugin_swc_js_minimizer" version = "0.1.0" diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index 8d2531e0231..0c3f718717a 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -263,6 +263,7 @@ export enum BuiltinPluginName { WebWorkerTemplatePlugin = 'WebWorkerTemplatePlugin', MergeDuplicateChunksPlugin = 'MergeDuplicateChunksPlugin', SplitChunksPlugin = 'SplitChunksPlugin', + SplitSharedModulesPlugin = 'SplitSharedModulesPlugin', ShareRuntimePlugin = 'ShareRuntimePlugin', ContainerPlugin = 'ContainerPlugin', ContainerReferencePlugin = 'ContainerReferencePlugin', diff --git a/crates/rspack_binding_options/Cargo.toml b/crates/rspack_binding_options/Cargo.toml index 262d7f76624..1c716ba724e 100644 --- a/crates/rspack_binding_options/Cargo.toml +++ b/crates/rspack_binding_options/Cargo.toml @@ -65,6 +65,7 @@ rspack_plugin_runtime_chunk = { version = "0.1.0", path = "../rspack_p rspack_plugin_schemes = { version = "0.1.0", path = "../rspack_plugin_schemes" } rspack_plugin_size_limits = { version = "0.1.0", path = "../rspack_plugin_size_limits" } rspack_plugin_split_chunks = { version = "0.1.0", path = "../rspack_plugin_split_chunks" } +rspack_plugin_split_shared_modules = { version = "0.1.0", path = "../rspack_plugin_split_shared_modules" } rspack_plugin_swc_js_minimizer = { version = "0.1.0", path = "../rspack_plugin_swc_js_minimizer" } rspack_plugin_warn_sensitive_module = { version = "0.1.0", path = "../rspack_plugin_warn_sensitive_module" } rspack_plugin_wasm = { version = "0.1.0", path = "../rspack_plugin_wasm" } diff --git a/crates/rspack_binding_options/src/options/raw_builtins/mod.rs b/crates/rspack_binding_options/src/options/raw_builtins/mod.rs index eff25843495..4638b7309d5 100644 --- a/crates/rspack_binding_options/src/options/raw_builtins/mod.rs +++ b/crates/rspack_binding_options/src/options/raw_builtins/mod.rs @@ -68,6 +68,7 @@ use rspack_plugin_runtime::{ use rspack_plugin_runtime_chunk::RuntimeChunkPlugin; use rspack_plugin_schemes::{DataUriPlugin, FileUriPlugin}; use rspack_plugin_size_limits::SizeLimitsPlugin; +use rspack_plugin_split_shared_modules::SplitSharedModulesPlugin; use rspack_plugin_swc_js_minimizer::SwcJsMinimizerRspackPlugin; use rspack_plugin_warn_sensitive_module::WarnCaseSensitiveModulesPlugin; use rspack_plugin_wasm::{ @@ -126,6 +127,7 @@ pub enum BuiltinPluginName { WebWorkerTemplatePlugin, MergeDuplicateChunksPlugin, SplitChunksPlugin, + SplitSharedModulesPlugin, ShareRuntimePlugin, ContainerPlugin, ContainerReferencePlugin, @@ -300,6 +302,9 @@ impl BuiltinPlugin { let options = downcast_into::(self.options)?.into(); plugins.push(SplitChunksPlugin::new(options).boxed()); } + BuiltinPluginName::SplitSharedModulesPlugin => { + plugins.push(SplitSharedModulesPlugin::new().boxed()); + } BuiltinPluginName::ShareRuntimePlugin => { plugins.push(ShareRuntimePlugin::new(downcast_into::(self.options)?).boxed()) } diff --git a/crates/rspack_plugin_split_chunks/src/plugin/mod.rs b/crates/rspack_plugin_split_chunks/src/plugin/mod.rs index 10ff9632d79..cfc0818a6cf 100644 --- a/crates/rspack_plugin_split_chunks/src/plugin/mod.rs +++ b/crates/rspack_plugin_split_chunks/src/plugin/mod.rs @@ -164,7 +164,9 @@ impl Debug for SplitChunksPlugin { #[plugin_hook(CompilationOptimizeChunks for SplitChunksPlugin, stage = Compilation::OPTIMIZE_CHUNKS_STAGE_ADVANCED)] fn optimize_chunks(&self, compilation: &mut Compilation) -> Result> { + let now = std::time::Instant::now(); self.inner_impl(compilation)?; + dbg!(now.elapsed().as_millis()); Ok(None) } diff --git a/crates/rspack_plugin_split_shared_modules/Cargo.toml b/crates/rspack_plugin_split_shared_modules/Cargo.toml new file mode 100644 index 00000000000..aa541a5b06d --- /dev/null +++ b/crates/rspack_plugin_split_shared_modules/Cargo.toml @@ -0,0 +1,22 @@ +[package] +edition = "2021" +name = "rspack_plugin_split_shared_modules" +version = "0.1.0" + +[lints] +workspace = true + +[dependencies] +dashmap = { workspace = true } +derivative = { workspace = true } +rayon = { workspace = true } +regex = { workspace = true } +rspack_collections = { version = "0.1.0", path = "../rspack_collections" } +rspack_core = { version = "0.1.0", path = "../rspack_core" } +rspack_error = { version = "0.1.0", path = "../rspack_error" } +rspack_hash = { version = "0.1.0", path = "../rspack_hash" } +rspack_hook = { version = "0.1.0", path = "../rspack_hook" } +rspack_regex = { version = "0.1.0", path = "../rspack_regex" } +rspack_util = { version = "0.1.0", path = "../rspack_util" } +rustc-hash = { workspace = true } +tracing = { workspace = true } diff --git a/crates/rspack_plugin_split_shared_modules/LICENSE b/crates/rspack_plugin_split_shared_modules/LICENSE new file mode 100644 index 00000000000..46310101ad8 --- /dev/null +++ b/crates/rspack_plugin_split_shared_modules/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2022-present Bytedance, Inc. and its affiliates. + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/rspack_plugin_split_shared_modules/src/lib.rs b/crates/rspack_plugin_split_shared_modules/src/lib.rs new file mode 100644 index 00000000000..1c2331ce202 --- /dev/null +++ b/crates/rspack_plugin_split_shared_modules/src/lib.rs @@ -0,0 +1,88 @@ +use std::sync::Arc; + +use rspack_core::{ + Chunk, ChunkUkey, Compilation, CompilationOptimizeChunks, ModuleIdentifier, Plugin, +}; +use rspack_error::Result; +use rspack_hook::{plugin, plugin_hook}; +use rustc_hash::FxHashMap; + +#[derive(Debug)] +#[plugin] +pub struct SplitSharedModulesPlugin {} + +impl SplitSharedModulesPlugin { + pub fn new() -> Self { + Self { + inner: Arc::new(SplitSharedModulesPluginInner {}), + } + } +} + +#[plugin_hook(CompilationOptimizeChunks for SplitSharedModulesPlugin)] +fn optimize_chunks(&self, compilation: &mut Compilation) -> Result> { + let now = std::time::Instant::now(); + let module_graph = compilation.get_module_graph(); + let chunk_graph = &compilation.chunk_graph; + + let mut chunk_map: FxHashMap, Vec> = FxHashMap::default(); + + for identifier in module_graph.modules().keys() { + let chunks = chunk_graph.get_module_chunks(*identifier); + let mut sorted_chunks = chunks.into_iter().copied().collect::>(); + sorted_chunks.sort(); + chunk_map + .entry(sorted_chunks) + .or_default() + .push(*identifier); + } + + for (chunks, modules) in chunk_map { + if chunks.len() <= 1 { + continue; + } + + // split chunks from original chunks and create new chunk + let mut new_chunk = Chunk::new(None, rspack_core::ChunkKind::Normal); + new_chunk.chunk_reason = Some("modules are shared across multiple chunks".into()); + let new_chunk_ukey = new_chunk.ukey; + compilation.chunk_graph.add_chunk(new_chunk_ukey); + + for chunk_ukey in &chunks { + let origin = compilation.chunk_by_ukey.expect_get_mut(chunk_ukey); + origin.split(&mut new_chunk, &mut compilation.chunk_group_by_ukey); + } + + compilation.chunk_by_ukey.add(new_chunk); + + for m in modules { + for chunk_ukey in &chunks { + compilation + .chunk_graph + .disconnect_chunk_and_module(chunk_ukey, m); + } + + compilation + .chunk_graph + .connect_chunk_and_module(new_chunk_ukey, m); + } + } + + dbg!(now.elapsed().as_millis()); + Ok(None) +} + +impl Plugin for SplitSharedModulesPlugin { + fn apply( + &self, + ctx: rspack_core::PluginContext<&mut rspack_core::ApplyContext>, + _options: &rspack_core::CompilerOptions, + ) -> rspack_error::Result<()> { + ctx + .context + .compilation_hooks + .optimize_chunks + .tap(optimize_chunks::new(self)); + Ok(()) + } +} diff --git a/packages/rspack/src/builtin-plugin/SplitSharedModulesPlugin.ts b/packages/rspack/src/builtin-plugin/SplitSharedModulesPlugin.ts new file mode 100644 index 00000000000..5fe1cf44123 --- /dev/null +++ b/packages/rspack/src/builtin-plugin/SplitSharedModulesPlugin.ts @@ -0,0 +1,14 @@ +import { BuiltinPluginName } from "@rspack/binding"; + +import { create } from "./base"; + +const map = { + foo: "dts" +}; + +export const SplitSharedModulesPlugin = create( + BuiltinPluginName.SplitSharedModulesPlugin, + () => { + return {}; + } +); diff --git a/packages/rspack/src/builtin-plugin/index.ts b/packages/rspack/src/builtin-plugin/index.ts index 25182ec9b0f..a8b6e0054f6 100644 --- a/packages/rspack/src/builtin-plugin/index.ts +++ b/packages/rspack/src/builtin-plugin/index.ts @@ -57,6 +57,7 @@ export * from "./SideEffectsFlagPlugin"; export * from "./SizeLimitsPlugin"; export * from "./SourceMapDevToolPlugin"; export * from "./SplitChunksPlugin"; +export * from "./SplitSharedModulesPlugin"; export * from "./LightningCssMiminizerRspackPlugin"; export * from "./SwcJsMinimizerPlugin"; export * from "./WarnCaseSensitiveModulesPlugin"; diff --git a/packages/rspack/src/exports.ts b/packages/rspack/src/exports.ts index dbaf7862258..4ccc55d4b74 100644 --- a/packages/rspack/src/exports.ts +++ b/packages/rspack/src/exports.ts @@ -170,15 +170,19 @@ export const webworker: Webworker = { WebWorkerTemplatePlugin }; import { LimitChunkCountPlugin } from "./builtin-plugin"; import { RuntimeChunkPlugin } from "./builtin-plugin"; import { SplitChunksPlugin } from "./builtin-plugin"; +import { SplitSharedModulesPlugin } from "./builtin-plugin"; + interface Optimize { LimitChunkCountPlugin: typeof LimitChunkCountPlugin; RuntimeChunkPlugin: typeof RuntimeChunkPlugin; SplitChunksPlugin: typeof SplitChunksPlugin; + SplitSharedModulesPlugin: typeof SplitSharedModulesPlugin; } export const optimize: Optimize = { LimitChunkCountPlugin, RuntimeChunkPlugin, - SplitChunksPlugin + SplitChunksPlugin, + SplitSharedModulesPlugin }; import { ModuleFederationPlugin } from "./container/ModuleFederationPlugin";