Repository navigation
Conversation
Tests PassedCommit: 9203f05 |
|
Reviewed 26fd598 |
26fd598 to
1a13763
Compare
### What? Makes `ChunkGroupInfo` resolve chunk groups through their canonical `ChunkGroupKey` identity. ### Why? Merged chunk groups carry an entry list whose order is intentionally unspecified. Looking up the full `ChunkGroup` value therefore makes identity depend on incidental entry ordering, even though the module graph already indexes groups by parent and merge tag. This is PR 1 of an eight-PR stack (on top of #99100, already merged): 1. **This PR** — canonical chunk-group identity 2. #99686 — chunk several chunk groups in one `chunk_group` call 3. #99749 — register the chunk groups that are chunked 4. #98734 — use graph-derived merged groups 5. #99750 — chunking takes a `ChunkGroupKey` 6. #98823 — Pages Router dev client graph chaining 7. #98685 — chunk-group availability bitmaps 8. #99050 — keyed chunk-group index lookups It provides stable identity for the graph-derived chunk-group work above it. ### How? Adds a canonical key conversion for every chunk-group variant and uses the existing `chunk_group_keys` index for lookup. Merged keys intentionally contain only their parent group and merge tag. ### Verification - `cargo test -p turbopack-core` — 160 passed - `cargo test -p turbopack-tests --test snapshot` — 125 passed - Affected Cargo checks and Rust formatting <!-- NEXT_JS_LLM --> <!-- fleet 4f67c6f7-bbe2-454b-9a17-822a60ef8f7e --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
…ll (#99686) ### What? `ChunkingContext::chunk_group` takes a list of chunk groups and chunks them together, as one chunk group, in a single chunking task. It produces one `ChunkGroupResult` (one `make_chunks` run) for the whole list. `evaluated_chunk_group`, `entry_chunk_group` and the `ChunkingContextExt` helpers still take a single group and wrap it in a one-element list. All existing callers pass a one-element list, so **this PR changes no output** (zero snapshot changes). ### Why? Review feedback on #98734 ([1](#98734 (comment)), [2](#98734 (comment))): a layout segment's client references can derive several isolated merged chunk groups, and #98734 chunked each one with a separate `chunk_group` call. That costs an extra chunking task per group and threads availability by hand. This PR adds the list form; #98734 then calls `chunk_group` once per segment and side with all of the segment's derived groups. ### How? `make_chunk_group` / `chunk_group_content` / `chunk_group_content_operation` take the list as a `SmallVec<[ChunkGroup; 1]>`: almost every call passes a single group, so the common case doesn't heap-allocate. The list crosses turbo-task boundaries, so this PR also adds `impl TaskInput for SmallVec<A>` in turbo-tasks, mirroring the `Vec` impl. Semantics: - **Traversal:** the entries of each group, in list order, with one shared traversal state. A module already reached from an earlier group isn't collected again, which matches what the previous one-call-per-group sequence produced (CSS order unchanged). - **Availability:** every listed group is added to the resulting availability info. - **Entry groups:** if several `Entry` groups are listed, the active page entries and the resulting entry group are the union of their modules, in list order. - **Edge cases:** an empty list is an error; groups listed more than once (same key) are chunked once. Unit tests cover the edge cases, the `Entry` union, and `SmallVec` as a task input (inline and spilled). This is PR 2 of an eight-PR stack (on top of #99100, already merged): 1. #98733 — canonical chunk-group identity 2. **This PR** — chunk several chunk groups in one `chunk_group` call 3. #99749 — register the chunk groups that are chunked 4. #98734 — use graph-derived merged groups 5. #99750 — chunking takes a `ChunkGroupKey` 6. #98823 — Pages Router dev client graph chaining 7. #98685 — chunk-group availability bitmaps 8. #99050 — keyed chunk-group index lookups <!-- NEXT_JS_LLM --> <!-- fleet 4f67c6f7-bbe2-454b-9a17-822a60ef8f7e --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
### What? Registers chunk groups in the module graph exactly as output code chunks them, in two places: - **Pages layout segments**: `_document` and `_app` are registered as their own `Entry` groups (`layout_chunk_group_entry`), in both the per-page (dev) graph chain and the whole-app `entries()`. `internal_ssr_chunk` chunks them as `ChunkGroup::Entry(vec![layout])` instead of `ChunkGroup::Shared(layout)`. - **turbopack-cli dev**: `create_web_entry_source` registers one `Entry` group per entry, with the same entries `DevHtmlAsset` chunks: the runtime entries followed by the entry for evaluatable modules, or the module alone otherwise. It passes `runtime_entries` without `with_entry(entry)`, because `DevHtmlAsset` appends the entry itself. ### Why? A chunk group is identified by its key, and for an `Entry` group that is its ordered entries. The next PR in the stack looks every chunked group up in `ChunkGroupInfo` by key, so a chunked group the graph didn't register fails. - Pages layouts were declared `Shared(layout)`. The whole-app graph also registers the standalone `/_document` and `/_app` endpoints' `Entry([module])` group for the same module and keeps only one root group per module, so `Shared(layout)` wasn't registered there. - turbopack-cli registered a single `Entry` group with all entries and runtime entries, while `DevHtmlAsset` chunks one group per entry (with the entry appended twice). No output change is intended: the same modules are chunked, but the registered groups now match them. This is PR 3 of an eight-PR stack (on top of #99100, already merged): 1. #98733 — canonical chunk-group identity 2. #99686 — chunk several chunk groups in one `chunk_group` call 3. **This PR** — register the chunk groups that are chunked 4. #98734 — use graph-derived merged groups 5. #99750 — chunking takes a `ChunkGroupKey` 6. #98823 — Pages Router dev client graph chaining 7. #98685 — chunk-group availability bitmaps 8. #99050 — keyed chunk-group index lookups ### Verification - Workspace `cargo clippy --all-targets -D warnings`, `cargo check --workspace --all-targets`, `cargo fmt --check` - Turbopack snapshots: 132 passed, no snapshot changes - `turbo-tasks` + `turbopack-core` + `next-core` unit tests: 362 passed - Turbopack production and dev `404-page-router` (custom `_document` / `_app`): 25/25, 12/12 <!-- NEXT_JS_LLM --> <!-- fleet 4f67c6f7-bbe2-454b-9a17-822a60ef8f7e --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
1a13763 to
3578cc3
Compare
3578cc3 to
6063005
Compare
…8823) ### What? Chains the Pages Router **development** client module graph so that `/_app` and the page being chunked live in the same `ModuleGraph`. This is PR 1 of a six-PR stack, based on `canary` (#98733, #99686 and #99749 are already merged): 1. **This PR** — Pages Router dev client graph chaining 2. #99910 — regression test for Pages Router `_app` availability (#99789) 3. #98734 — use graph-derived merged groups 4. #99750 — chunking takes a `ChunkGroupKey` 5. #98685 — chunk-group availability bitmaps 6. #99050 — keyed chunk-group index lookups ### Why? In development `Project::per_page_module_graph()` is true, so every page endpoint builds its own client `ModuleGraph`, each with its own `ChunkGroupInfo` and its own index space starting at `0`. `/_app` is always loaded before the page in the browser, so the page reuses `/_app`'s availability when chunking. Today that availability is a set of concrete modules, which is graph-independent, so the split graphs go unnoticed. That stops being true as soon as availability is expressed as chunk-group indices (#98685): `/_app` would record index `0` in *its* graph, and the page would resolve that same `0` against *its own* graph — where it means the page's own entry group. Every module of the group being chunked then looks already-available and is dropped, producing runtime errors like: ``` Module [hmr-entry]/hmr-entry.js { ENTRY => "[project]/pages/_error" } was instantiated as a runtime entry of chunk …, but the module factory is not available. ``` ### How? - `PageEndpoint::client_module_graph` builds a graph *chain* in per-page mode: `/_app`'s entry group first, then the page's, with `VisitedModules` threaded between them — the same layout-segment approach `ssr_module_graph` already uses for document → app → page. - Whole-app mode (production) is unchanged and still goes through `module_graph_for_modules`. - `/_app` itself is not chained onto anything. - Extracts a small `client_entry_modules` helper, since the entry module list is now needed in two places. Availability is still carried as the set of modules `/_app` emitted. Making the two endpoints share one graph also fixes #99789: with separate graphs, batching in the page graph could produce a node for a module shared with `/_app` whose edges point at page-only modules, and those modules were skipped as already available ("module factory is not available" on hydration). #99910 adds the regression test. Lining up the graph identities is also what #98685 builds on. ### Verification - `turbopack-core` — 160 passed - `next-core` — 84 passed - Turbopack snapshots — 125 passed - `404-page-router` and `server-navigation-error` development suites - Affected Cargo checks and Rust formatting <!-- NEXT_JS_LLM --> <!-- fleet 4f67c6f7-bbe2-454b-9a17-822a60ef8f7e --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
…99910) ### What? Regression coverage for #99789, stacked on #98823. This is PR 2 of a six-PR stack, based on `canary`: 1. #98823 — Pages Router dev client graph chaining 2. **This PR** — regression test for Pages Router `_app` availability (#99789) 3. #98734 — use graph-derived merged groups 4. #99750 — chunking takes a `ChunkGroupKey` 5. #98685 — chunk-group availability bitmaps 6. #99050 — keyed chunk-group index lookups - **New e2e suite** `test/e2e/pages-app-availability-boundary`: a Pages Router app with the import shape from #99789: - `_app` imports `c-shared` and dynamically imports a module that also imports it. - `pages/index` imports `a-page-only`, `b-boundary`, `c-shared`, `d-boundary`, `e-page`, and dynamically imports a module that imports `b`/`d`, which makes `b` and `d` batching boundaries. The test asserts that the page hydrates (a marker set in `useEffect`), that every imported value renders, and that no "module factory is not available" error is logged. - **`app-document-import-order`**: the "loads modules shared by _app and the page only once" assertion added in #97664 was skipped in all of dev. It now runs everywhere and is gated `// @gate !(turbopack && dev)` (expected failure). ### Why? In Turbopack dev, #97664 seeds each page's client chunk group with `/_app`'s availability. But each page had its own module graph, so availability items (`ModuleOrBatch` identities) were compared across graphs. In the page graph, batching can produce a `ModuleOrBatch::Module(c-shared)` node whose edges point at earlier siblings (`a`, `b`). Because `/_app` already has `Module(c-shared)`, the traversal excluded that subtree, and `a`/`b` were never chunked. Hydration then fails with `Module [project]/lib/a-page-only.js … was instantiated … but the module factory is not available`. No test covered this, because the #97664 test was skipped in dev. #98823 chains `/_app` and the page into one dev client module graph, which fixes the missing modules. It does **not** yet dedupe the `app-document-import-order` fixture in Turbopack dev. The shared module is still emitted in both the `_app` and the page chunk, because the page's batches in the combined graph carry different chunk-group bitmaps than `_app`'s own graph, so they don't match `_app`'s availability items. Hence the gate. It stays valid through #98734 and #99750; #98685 (higher in this stack) dedupes the fixture in Turbopack dev, turning it into a "stale gate", and removes it. ### How was it verified? Local JS build of this branch plus the preview native binaries of #98823's head (`8dd759a`) and its base (`26fd598`) in `packages/next-swc/native`. The two commits differ only in Rust. | Suite | Mode | #98823 base | #98823 head | |---|---|---|---| | `pages-app-availability-boundary` | Turbopack dev | ❌ module factory not available | ✅ | | `pages-app-availability-boundary` | Turbopack start, webpack dev/start | | ✅ | | `app-document-import-order` | Turbopack dev | | ✅ (gated: failed as expected) | | `app-document-import-order` | webpack dev, Turbopack/webpack start | | ✅ | After restacking onto canary, with native builds of each layer, in Turbopack dev: - This PR's tree: `pages-app-availability-boundary` ✅, `app-document-import-order` ✅ (gated: failed as expected). - #98734 and #99750: gated assertion still fails as expected. - #98685 and #99050: gate stale, so #98685 removes it; ungated, both suites ✅. Fixes #99789 (together with #98823) <!-- NEXT_JS_LLM --> <!-- fleet a7614af3-e186-4297-beaf-7e55eaddc0da --> <!-- fleet 4f67c6f7-bbe2-454b-9a17-822a60ef8f7e --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
6063005 to
0df91ea
Compare
|
I'm curious if this is actually a requirement for the rest of the stack, or just a way to prevent random stuff from being chunked? |
|
Mostly the latter. It isn't a hard build or ordering requirement for #98685/#99050, but it closes a hole that #98685 makes dangerous. Not a hard requirement: I applied #98685 directly on #98734, without this PR. Turbopack core and the chunking contexts compile unchanged. The only error is an import in AvailabilityInfo::root().with_chunk_group(module_graph, ChunkGroupKey::Entry(app_entries))What #98685 actually depends on is that every chunked group is registered in Why it still matters with #98685: before this PR, chunking already looked up each group by key (and errored if it wasn't registered), but traversed the entries the caller passed in. Today a mismatch there is mostly harmless, because availability is the set of modules that were actually emitted. With #98685, availability becomes "group index i is available", and module availability is derived from the registered group's membership bitmap, not from what was emitted. If a caller passed a Taking only a So: #98685 would work today if all callers happen to pass the registered entries (they do after #98734), but this PR turns "callers must not hand-build groups" from a convention into something the type system enforces. |
|
In short: It encodes on type-level that only registered chunk groups can be chunked. |
0df91ea to
7a2b098
Compare
|
The chunking context I recently added caused a merge conflict |
Make chunking APIs take ChunkGroupKey values and resolve the registered chunk groups from ChunkGroupInfo, so callers cannot supply group contents that disagree with the module graph. Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
7a2b098 to
9203f05
Compare
### What? Next.js output chunking uses the merged chunk groups derived by `ChunkGroupInfo`, rather than reconstructing them in output code: - **Client references** (`app_client_references_chunks.rs`): each layout segment chunks the isolated merged client reference groups the graph derived for its references, with one `chunk_group` call per segment and side (`ssr modules`, `client modules`, using the list form from #99686). There is no manual availability threading or result concatenation. - **Server utilities** (`app.rs`, per-page module graph): the entry's server utilities are chunked as the graph's shared merged group for the entry and the server utility merge tag, instead of a hand-built `SharedMerged` group. - **Ordered traversal**: chunk traversal starts from `ModuleBatchesGraph::get_ordered_entries` for each group, in list order. The chunking API still takes `ChunkGroup` values; this PR passes the groups exactly as the graph registered them. #99750 then switches the API to keys. This is PR 3 of a six-PR stack, based on `canary` (#98733, #99686 and #99749 are already merged): 1. #98823 — Pages Router dev client graph chaining 2. #99910 — regression test for Pages Router `_app` availability (#99789) 3. **This PR** — use graph-derived merged groups 4. #99750 — chunking takes a `ChunkGroupKey` 5. #98685 — chunk-group availability bitmaps 6. #99050 — keyed chunk-group index lookups ### Why? The module graph derives merged parents from actual membership bitmaps. Output code previously guessed a parent and assembled an entry list by hand, which could disagree with the graph for shared client references and framework routes. A segment's client references can derive several merged groups (e.g. one per page rendering the root layout). Chunking each one separately ran one chunking task per group; chunking them together runs one per segment and side. Graph-derived merged entries have unspecified raw order, so chunk traversal must use the postorder `ModuleBatchesGraph` already computes, to preserve CSS evaluation order. ### How? - `ChunkGroupInfo` exposes exact memberships (`get_chunk_groups_for_module`) and the shared merged child of a parent and merge tag (`get_shared_merged_chunk_group`). - `ClientReference` retains its source module (`parent_module`), so merge-tagged groups are scoped to their graph parent. Component-owned references are intersected with their server component's memberships, which prevents collecting across segments, falling back to the source module's groups when the intersection is empty. - The client reference merge tags are shared `pub const`s in `next_client_reference` (`CLIENT_MERGE_TAG`, used by CSS and ecmascript client references, and `SSR_MERGE_TAG`), so producers and the chunking code match on the same values. - The `server side rendering` / `client side rendering` tracing spans are kept around the per-segment chunking. ### Verification - Workspace `cargo clippy --all-targets -D warnings`, `cargo check --workspace --all-targets`, `cargo fmt --check` - Turbopack snapshots: 132 passed, no snapshot changes - `turbo-tasks` + `turbopack-core` + `next-core` unit tests: 364 passed (incl. the new parent-index and merge-tag tests) - Production `layout-segment-chunk-sharing`, with a new assertion that the root layout's client and SSR modules are each chunked by one task. The suite now waits until the trace server has fully read the trace before querying spans. - Turbopack production and dev: `404-page-router`, `app-dir/actions/app-action`, `app-dir/initial-css-order`; production `edge-pages-support` <!-- NEXT_JS_LLM --> <!-- fleet 4f67c6f7-bbe2-454b-9a17-822a60ef8f7e --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
### What? Replaces Turbopack's retained available-module sets with compact chunk-group bitmaps. This is PR 5 of a six-PR stack, based on `canary` (#98733, #99686 and #99749 are already merged): 1. #98823 — Pages Router dev client graph chaining 2. #99910 — regression test for Pages Router `_app` availability (#99789) 3. #98734 — use graph-derived merged groups 4. #99750 — chunking takes a `ChunkGroupKey` 5. **This PR** — chunk-group availability bitmaps 6. #99050 — keyed chunk-group index lookups ### Why? `AvailabilityInfo` previously retained every concrete module, module batch, and async-loader item emitted by ancestor chunk groups. The same module lists were duplicated across availability chains, increasing memory use for large graphs. The module graph already records which chunk groups contain each module. Retaining only available group indices lets availability checks reuse that information. ### How? - Stores available chunk-group indices in a roaring bitmap (`AvailableChunkGroups`), replacing `AvailableModules`. - Determines module availability by intersecting its `ChunkGroupInfo` membership bitmap with the available groups. - Uses a batch's representative first module, because every module in a batch has identical group membership. - Tracks async-loader membership by the parent groups that emit each loader, preserving nested loader deduplication. - Scopes availability to its `ModuleGraph`: chunk-group indices only mean something inside one graph's `ChunkGroupInfo`, so the graph is stored alongside the bitmap and asserted on every use, and `translate` re-expresses availability in another graph by mapping indices through their canonical `ChunkGroupKey`s. Groups the target graph doesn't have are dropped, which can only duplicate a module, never omit one. Behavior is unchanged. Availability-dependent asset identifiers are derived from the *identity* of the available chunk groups — each group rendered via `ChunkGroupKey::ident_str` (module idents plus merge tags, merged parents resolved recursively) and sorted — never from the raw graph-local indices, which are only meaningful within a single `ModuleGraph` and would collide across the per-endpoint graphs used in development. Deterministic filenames change as a result; the snapshots are updated accordingly and the module IDs they contain are identical. One observable improvement: in Turbopack dev, `/_app` and the page are chunked in the same graph (#98823), and availability is now tracked by chunk-group membership, so modules shared by both are loaded only once. The `// @gate !(turbopack && dev)` that #99910 put on the "loads modules shared by _app and the page only once" assertion in `app-document-import-order` is therefore stale, and this PR removes it. ### Verification - `turbopack-core` — 164 passed - `next-core` — 84 passed - Turbopack snapshots — 126 passed - Nested async availability coverage verifies shared-module and loader deduplication - Pages/App development and production suites, including `404-page-router`, `server-navigation-error`, client-reference chunking, and full `css-order` - Turbopack dev `app-document-import-order` (now ungated) and `pages-app-availability-boundary`, on this PR and on #99050 - Affected Cargo checks and Rust formatting ### Benchmarks Measured on commits `64b73c08` (this PR) against `c543a06d` (#98823) — the pre-rebase SHAs of this stack. The stack has since been rebased onto newer canary; the refactor's code diff is byte-identical across that rebase, so these numbers still characterise this patch, but they were not re-measured on the current heads. The two commits were one apart and differed only in Rust, so the JS build was shared and the two native binaries were swapped in place with their sha256 asserted before every run. `next build --turbopack`, cold (`rm -rf .next`) every run, **3 runs per side, sides interleaved**. Memory is the peak **aggregate RSS of the whole build process tree**, sampled from `/proc` every 50 ms (GNU `time -v` reports only the max of a single waited-for child, which understates a multi-process build). Median shown, with min–max in brackets. "disjoint" = the two sides' ranges do not overlap at all. **Workloads where availability actually engages** — it only does work when a chunk group inherits availability from a parent, i.e. dynamic imports or multiple entries: | Workload | Metric | base | this PR | Δ | | |---|---|---|---|---|---| | 200 routes × 6 dynamic imports | build | 108.7 s [105.7–111.6] | **75.9 s** [74.0–78.0] | **−30.1%** | disjoint | | | peak RSS | 3462 MB [3361–3467] | **3093 MB** [3052–3153] | **−10.7%** | disjoint | | 750 routes, no dynamic imports | build | 314.5 s [313.1–323.0] | **292.4 s** [289.4–311.2] | **−7.0%** | disjoint | | | peak RSS | 9220 MB [9212–9687] | 10904 MB [9196–11164] | — | inconclusive | **Control workloads** — single entry, no `import()`, so availability is effectively root everywhere and there is nothing to improve: | Workload | build Δ | peak RSS Δ | | |---|---|---|---| | `bench/module-cost` (~18.6k static modules) | −3.3% | 0.0% | noise | | `bench/heavy-npm-deps` (1 page, heavy deps) | −1.9% | −1.6% | noise | Build time improves monotonically with how much availability a workload exercises (−1.9%, −3.3%, −7.0%, −30.1%). Emitted `.js` file counts are identical between sides on every workload, consistent with unchanged behaviour. Caveats: the dynamic-import workload is synthetic and its 30% figure is not a typical-app claim; n=3 with no statistical test; aggregate RSS double-counts pages shared between processes; the 750-route memory result is bimodal on this PR's side (its own range spans the base median) and is therefore reported as inconclusive rather than a regression. The many-pages fixture was reduced from its stock 3000 routes to 750 because 3000 exceeds the 16 GB benchmark machine and thrashes, which would measure swap rather than chunking. <!-- NEXT_JS_LLM --> <!-- fleet 4f67c6f7-bbe2-454b-9a17-822a60ef8f7e --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
…9050) ### What? Narrows the turbo-tasks dependency of `ChunkGroupInfo::get_index_of` so that looking up one chunk group's index no longer depends on the entire `ChunkGroupInfo`. This is PR 6 of a six-PR stack, based on `canary` (#98733, #99686 and #99749 are already merged): 1. #98823 — Pages Router dev client graph chaining 2. #99910 — regression test for Pages Router `_app` availability (#99789) 3. #98734 — use graph-derived merged groups 4. #99750 — chunking takes a `ChunkGroupKey` 5. #98685 — chunk-group availability bitmaps 6. **This PR** — keyed chunk-group index lookups ### Why? #98685 makes availability a set of chunk-group *indices*, which turns `get_index_of` into a hot path: every availability translation between graphs resolves keys to indices through it. That lookup previously read `chunk_group_keys` off the whole `ChunkGroupInfo` value. In turbo-tasks, reading one field of a cell creates a dependency on the cell as a whole, so every index lookup in the graph was invalidated whenever *any* unrelated part of the chunk group info changed — a new async chunk group anywhere in the app re-ran every lookup, and everything downstream of them. The wider the availability work built on top of it, the more that costs. ### How? Adds `ChunkGroupKeyToIndex`, a `cell = "keyed"` map from `ChunkGroupKey` to its index, alongside the existing set, exposed through an argument-less `chunk_group_indices()` task. `get_index_of` reads a single key from it through `Vc::get`, so its caller depends only on the key it actually asked for: an unrelated chunk group changing no longer invalidates it. `get_index_of` itself is deliberately a plain async method rather than a turbo-tasks function. As a function it would create one task per chunk group argument, which is exactly the per-chunk-group overhead this change is meant to remove; the single `chunk_group_indices()` task is what provides the caching. The interesting constraint is why this can't reuse the `FxIndexSet<ChunkGroupKey>` that was already there — a set that already holds exactly these keys in exactly this order. Two properties of the keyed-cell traits rule it out: - `KeyedAccess for IndexSet` yields `()`, not the index, so the value needed isn't retrievable through a keyed read. - `KeyedEq for IndexSet` compares *membership* and ignores position. A key whose index merely shifted — same set, different order — would not be reported as changed, and lookups would keep returning a stale index. That is a correctness bug, not just a missed invalidation. Mapping the key to the index as the *value* makes `KeyedEq for HashMap` compare indices, which detects exactly that shift. The `FxIndexSet` stays for the reverse (index → key) direction, so the duplication is deliberate and documented at the type. The debug-only "couldn't find chunk group" diagnostic still reads the whole struct to render its message. That path is only taken on failure, so the happy path keeps its narrow dependency. ### Verification Keyed-cell invalidation semantics (per-key dependencies, `KeyedEq` comparing values) are covered by turbo-tasks' own tests, so this PR does not add a dedicated invalidation test. - `cargo clippy --workspace --all-targets -- -D warnings -A deprecated` - `turbopack-core` + `next-core` — 252 passed - Turbopack snapshots — 126 passed, no output churn - `cargo fmt --check` clean Behavior is unchanged; this is a dependency-graph refactor with no effect on emitted output. <!-- fleet 4f67c6f7-bbe2-454b-9a17-822a60ef8f7e --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
What?
Chunking APIs take chunk group keys instead of chunk groups:
ChunkingContext::chunk_grouptakeschunk_groups: SmallVec<[ChunkGroupKey; 1]>.evaluated_chunk_group,entry_chunk_groupand allChunkingContextExthelpers takechunk_group: ChunkGroupKey.ChunkGroupInfo::get_chunk_group(key)and chunk the registered group, using its entries for evaluation and bootstrapping.ChunkGroupKey::{Entry, Async, Isolated, Shared}, or the.key()of a graph-derived merged group.Why?
Chunking already resolved every group by key and failed if the group wasn't registered, so the entries a caller passed in were redundant at best and inconsistent with the graph at worst. With key-only APIs,
ChunkGroupInfois the single source of a group's contents, and callers can no longer construct a group that disagrees with the module graph.This is PR 4 of a six-PR stack, based on
canary(#98733, #99686 and #99749 are already merged):_appavailability (Turbopack dev (16.4.0): Pages Router page modules missing ("module factory is not available") after _app availability seeding (#97664) #99789)ChunkGroupKeyVerification
cargo clippy --all-targets -D warnings, per-layercargo fmtand workspace check404-page-router,app-dir/actions/app-actionandapp-dir/initial-css-order; productionedge-pages-support,app-dir/css-orderandlayout-segment-chunk-sharing