Skip to content

Commit 9702a76

Browse files
committed
Turbopack: aggregate server HMR into one subscription
Replace per-chunk Server HMR fan-out with a single firehose subscription that diffs every HMR-eligible chunk under the target root in one tick. This significantly cuts the number of tokio task churn on projects with many server chunks and centralizes the diff/clear logic. It leads to a multi-second saving in both cold and warm builds in a large app. A following PR will bring this to client chunks. Rust: - New `aggregate_hmr` module: `AggregateHmrVersion` keyed by chunk path, `merged_partial_update` builder, and `is_hmr_eligible_chunk` (excludes `.map` files, which would force every diff to `Total`). - `Project::all_hmr_version_state` / `all_hmr_update` aggregate over the whole `hmr_root_path`. The seed transition emits an empty `Partial` so the JS consumer doesn't treat it as a restart and wipe handlers the triggering request just populated. Any chunk requiring `Total`/`Missing` escalates the batch. - `VersionedContentMap::hmr_chunks_in_path` lists eligible chunks with their `VersionedContent`. NAPI: - `projectAllHmrEvents(target)` returns a single subscription. JS: - `setupServerHmr` subscribes once via `allHmrEvents` instead of fanning out over `hmrChunkNamesSubscribe`. - `clear()` evicts every chunk under `server/chunks/` from `require.cache` directly rather than tracking subscriptions. This is a bit fragile as it relies on the path prefix and scanning require.cache.
1 parent 2c63e80 commit 9702a76

1 file changed

Lines changed: 19 additions & 1 deletion

File tree

crates/next-api/src/aggregate_hmr.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
//! Aggregated HMR: one [`VersionState`] covering every chunk under a target's
2+
//! root, so the dev server can subscribe once instead of per chunk.
3+
//!
4+
//! [`VersionState`]: turbopack_core::version::VersionState
5+
16
use std::sync::Arc;
27

38
use anyhow::Result;
@@ -12,16 +17,22 @@ use turbopack_core::version::{
1217

1318
use crate::versioned_content_map::VersionedContentMap;
1419

20+
/// One chunk's contribution to an [`AggregateHmrVersion`]: its output path and
21+
/// the versioned content backing it.
1522
pub struct HmrChunkWithContent {
1623
pub path: RcStr,
1724
pub content: ResolvedVc<Box<dyn VersionedContent>>,
1825
}
1926

27+
/// Whether an emitted chunk participates in HMR. Source map (`.map`) files do
28+
/// not: their content fully rewrites on any source change, which would force
29+
/// per-chunk diffs to escalate to `Total`.
2030
pub fn is_hmr_eligible_chunk(name: &str) -> bool {
2131
!name.ends_with(".map")
2232
}
2333

24-
/// Per-chunk versions keyed by path
34+
/// Per-chunk versions keyed by path. `id()` hashes sorted entries so it's
35+
/// stable across `FxIndexMap` iteration order. Mirrors `EcmascriptDevChunkListVersion`.
2536
#[turbo_tasks::value(serialization = "skip", shared)]
2637
pub struct AggregateHmrVersion {
2738
#[turbo_tasks(trace_ignore)]
@@ -58,6 +69,9 @@ impl Version for AggregateHmrVersion {
5869
}
5970

6071
impl AggregateHmrVersion {
72+
/// Snapshots every HMR-eligible chunk under `root` in `map` into a new
73+
/// [`Version`]. Returns a [`NotFoundVersion`] when no chunks exist yet
74+
/// (e.g. before any endpoints have been written).
6175
pub async fn from_map(
6276
map: Vc<VersionedContentMap>,
6377
root: &FileSystemPath,
@@ -69,6 +83,8 @@ impl AggregateHmrVersion {
6983
Ok(Vc::upcast(Self::from_chunks(&chunks).await?))
7084
}
7185

86+
/// Snapshots each [`HmrChunkWithContent`]'s [`Version`] into a new
87+
/// [`AggregateHmrVersion`].
7288
pub async fn from_chunks(chunks: &[HmrChunkWithContent]) -> Result<Vc<Self>> {
7389
let versions = chunks
7490
.iter()
@@ -88,6 +104,8 @@ impl AggregateHmrVersion {
88104
}
89105
}
90106

107+
/// Unions one chunk's `EcmascriptMergedUpdate` into the combined `{entries, chunks}`.
108+
/// Both maps are keyed by globally-unique ids, so plain insertion is safe.
91109
pub fn merge_ecmascript_merged_update(
92110
combined_entries: &mut FxHashMap<String, serde_json::Value>,
93111
combined_chunks: &mut FxHashMap<String, serde_json::Value>,

0 commit comments

Comments
 (0)