diff --git a/crates/rspack_core/src/compilation/create_chunk_assets/mod.rs b/crates/rspack_core/src/compilation/create_chunk_assets/mod.rs index ee58998ec05f..ca1854d798d5 100644 --- a/crates/rspack_core/src/compilation/create_chunk_assets/mod.rs +++ b/crates/rspack_core/src/compilation/create_chunk_assets/mod.rs @@ -1,8 +1,12 @@ use async_trait::async_trait; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; use super::*; -use crate::{cache::Cache, compilation::pass::PassExt, logger::Logger}; +use crate::{ + cache::Cache, + compilation::{CompilationChunkAssetHook, pass::PassExt}, + logger::Logger, +}; pub struct CreateChunkAssetsPass; @@ -84,13 +88,16 @@ pub async fn create_chunk_assets( .chunk_by_ukey .contains(chunk) }); - let chunks: FxHashSet = mutations - .iter() - .filter_map(|mutation| match mutation { - Mutation::ChunkSetHashes { chunk } => Some(*chunk), - _ => None, - }) - .collect(); + let mut chunks = Vec::new(); + let mut seen_chunks = FxHashSet::default(); + for chunk in mutations.iter().filter_map(|mutation| match mutation { + Mutation::ChunkSetHashes { chunk } => Some(*chunk), + _ => None, + }) { + if seen_chunks.insert(chunk) { + chunks.push(chunk); + } + } tracing::debug!(target: incremental::TRACING_TARGET, passes = %IncrementalPasses::CHUNK_ASSET, %mutations); let logger = compilation.get_logger("rspack.incremental.chunkAsset"); logger.log(format!( @@ -105,7 +112,7 @@ pub async fn create_chunk_assets( .chunk_by_ukey .keys() .copied() - .collect() + .collect::>() }; let compilation_ref = &*compilation; let results = rspack_parallel::scope::<_, Result<_>>(|token| { @@ -114,7 +121,7 @@ pub async fn create_chunk_assets( let s = unsafe { token.used((compilation_ref, &plugin_driver, chunk)) }; s.spawn(|(this, plugin_driver, chunk)| async { - let mut manifests = Vec::new(); + let mut manifests = Vec::with_capacity(2); let mut diagnostics = Vec::new(); plugin_driver .compilation_hooks @@ -134,24 +141,93 @@ pub async fn create_chunk_assets( }) .await; - let mut chunk_render_results = ChunkRenderArtifact::default(); + let mut chunk_render_results = Vec::with_capacity(results.len()); for result in results { let item = result.to_rspack_result()?; let (key, value) = item?; - chunk_render_results.insert(key, value); + chunk_render_results.push((key, value)); } - let chunk_ukey_and_manifest = if compilation + + let has_chunk_asset_hook = + has_chunk_asset_taps(&plugin_driver.compilation_hooks.chunk_asset).await; + + if compilation .incremental .passes_enabled(IncrementalPasses::CHUNK_ASSET) { + let mut chunk_render_artifact = + FxHashMap::with_capacity_and_hasher(chunk_render_results.len(), Default::default()); + chunk_render_artifact.extend(chunk_render_results); compilation .chunk_render_artifact - .extend(chunk_render_results); - compilation.chunk_render_artifact.clone() - } else { - chunk_render_results - }; + .extend(ChunkRenderArtifact::from(chunk_render_artifact)); + let chunk_render_artifact = compilation.chunk_render_artifact.clone(); + if !has_chunk_asset_hook { + emit_chunk_assets_without_hook(compilation, chunk_render_artifact); + } else { + emit_chunk_assets_with_hook(compilation, chunk_render_artifact, plugin_driver).await; + } + return Ok(()); + } + + if !has_chunk_asset_hook { + emit_chunk_assets_without_hook(compilation, chunk_render_results); + return Ok(()); + } + + emit_chunk_assets_with_hook(compilation, chunk_render_results, plugin_driver).await; + + Ok(()) +} + +fn emit_chunk_assets_without_hook( + compilation: &mut Compilation, + chunk_ukey_and_manifest: impl IntoIterator, +) { + for ( + chunk_ukey, + ChunkRenderResult { + manifests, + diagnostics, + }, + ) in chunk_ukey_and_manifest + { + if !diagnostics.is_empty() { + compilation.extend_diagnostics(diagnostics); + } + + if manifests.is_empty() { + continue; + } + + let current_chunk = compilation + .build_chunk_graph_artifact + .chunk_by_ukey + .expect_get_mut(&chunk_ukey); + + current_chunk.set_rendered(true); + for file_manifest in &manifests { + if file_manifest.auxiliary { + current_chunk.add_auxiliary_file(file_manifest.filename.clone()); + } else { + current_chunk.add_file(file_manifest.filename.clone()); + } + } + + for file_manifest in manifests { + compilation.emit_asset( + file_manifest.filename, + CompilationAsset::new(Some(file_manifest.source), file_manifest.info), + ); + } + } +} +async fn emit_chunk_assets_with_hook( + compilation: &mut Compilation, + chunk_ukey_and_manifest: impl IntoIterator, + plugin_driver: SharedPluginDriver, +) { for ( chunk_ukey, ChunkRenderResult { @@ -160,7 +236,13 @@ pub async fn create_chunk_assets( }, ) in chunk_ukey_and_manifest { - compilation.extend_diagnostics(diagnostics); + if !diagnostics.is_empty() { + compilation.extend_diagnostics(diagnostics); + } + + if manifests.is_empty() { + continue; + } for file_manifest in manifests { let filename = file_manifest.filename; @@ -184,8 +266,6 @@ pub async fn create_chunk_assets( _ = chunk_asset(compilation, chunk_ukey, &filename, plugin_driver.clone()).await; } } - - Ok(()) } async fn chunk_asset( @@ -201,3 +281,20 @@ async fn chunk_asset( .await?; Ok(()) } + +async fn has_chunk_asset_taps(hook: &CompilationChunkAssetHook) -> bool { + if !hook.taps.is_empty() { + return true; + } + + for interceptor in hook.interceptors.iter() { + let Ok(taps) = interceptor.call(hook).await else { + return true; + }; + if !taps.is_empty() { + return true; + } + } + + false +}