Repository: vercel/next.js
Pull request: Use keyed cells for used_exports and export_circuit_breakers in BindingUsageInfo

## What?

Apply the `cell = "keyed"` pattern to `used_exports` (`FxHashMap`) and `export_circuit_breakers` (`FxHashSet`) in `BindingUsageInfo`, matching the existing pattern already used for `unused_references`.

## Why?

Previously, `used_exports` and `export_circuit_breakers` were stored as plain inline collections within `BindingUsageInfo`. Any change to any module's export usage would invalidate **all** callers of `used_exports()`, even those querying unrelated modules. This causes unnecessary recomputation during incremental rebuilds.

With keyed cells, lookups like `self.used_exports.get(&module)` and `self.export_circuit_breakers.contains_key(&module)` only invalidate callers that queried the specific module whose export usage changed, providing per-module invalidation granularity.

## How?

1. **New keyed transparent value types** — `UsedExportsMap` and `ExportCircuitBreakers` wrappers with `#[turbo_tasks::value(transparent, cell = "keyed")]`.

2. **Fields changed to `ResolvedVc`** — `used_exports` and `export_circuit_breakers` fields are now `ResolvedVc<UsedExportsMap>` and `ResolvedVc<ExportCircuitBreakers>` instead of inline `FxHashMap`/`FxHashSet`.

3. **`used_exports()` becomes a `#[turbo_tasks::function]`** — Moved into a `#[turbo_tasks::value_impl]` block so it's a tracked task function. Callers (`BrowserChunkingContext`, `NodeJsChunkingContext`) no longer need `.await?` — they call it directly and get a `Vc<ModuleExportUsage>`.

4. **Per-key lookups** — `contains_key(&module).await?` and `get(&module).await?` leverage the keyed cell pattern for fine-grained invalidation.

### Files changed

- `turbopack/crates/turbopack-core/src/module_graph/binding_usage_info.rs` — Core changes: new keyed types, field type changes, `used_exports()` as tracked function
- `turbopack/crates/turbopack-browser/src/chunking_context.rs` — Simplified call site (no `.await?`)
- `turbopack/crates/turbopack-nodejs/src/chunking_context.rs` — Simplified call site (no `.await?`)

Changed files:
filename: turbopack/crates/turbopack-core/src/module_graph/binding_usage_info.rs
patch: @@ -17,15 +17,21 @@ use crate::{
     resolve::{ExportUsage, ImportUsage},
 };
 
+#[turbo_tasks::value(transparent, cell = "keyed")]
+pub struct UsedExportsMap(FxHashMap<ResolvedVc<Box<dyn Module>>, ModuleExportUsageInfo>);
+
+#[turbo_tasks::value(transparent, cell = "keyed")]
+pub struct ExportCircuitBreakers(FxHashSet<ResolvedVc<Box<dyn Module>>>);
+
 #[turbo_tasks::value]
 #[derive(Clone, Default, Debug)]
 pub struct BindingUsageInfo {
     unused_references: ResolvedVc<UnusedReferences>,
     #[turbo_tasks(trace_ignore)]
     unused_references_edges: FxHashSet<GraphEdgeIndex>,
 
-    used_exports: FxHashMap<ResolvedVc<Box<dyn Module>>, ModuleExportUsageInfo>,
-    export_circuit_breakers: FxHashSet<ResolvedVc<Box<dyn Module>>>,
+    used_exports: ResolvedVc<UsedExportsMap>,
+    export_circuit_breakers: ResolvedVc<ExportCircuitBreakers>,
 }
 
 #[turbo_tasks::value(transparent)]
@@ -58,8 +64,8 @@ impl BindingUsageInfo {
         &self,
         module: ResolvedVc<Box<dyn Module>>,
     ) -> Result<Vc<ModuleExportUsage>> {
-        let is_circuit_breaker = self.export_circuit_breakers.contains(&module);
-        let Some(exports) = self.used_exports.get(&module) else {
+        let is_circuit_breaker = self.export_circuit_breakers.contains_key(&module).await?;
+        let Some(exports) = self.used_exports.get(&module).await? else {
             // There are some module that are codegened, but not referenced in the module graph,
             let ident = module.ident_string().await?;
             if ident.contains(".wasm_.loader.mjs") || ident.contains("/__nextjs-internal-proxy.") {
@@ -73,7 +79,7 @@ impl BindingUsageInfo {
             bail!("export usage not found for module: {ident:?}");
         };
         Ok(ModuleExportUsage {
-            export_usage: exports.clone().resolved_cell(),
+            export_usage: (*exports).clone().resolved_cell(),
             is_circuit_breaker,
         }
         .cell())
@@ -291,8 +297,8 @@ pub async fn compute_binding_usage_info(
         Ok(BindingUsageInfo {
             unused_references: ResolvedVc::cell(unused_references),
             unused_references_edges,
-            used_exports,
-            export_circuit_breakers,
+            used_exports: ResolvedVc::cell(used_exports),
+            export_circuit_breakers: ResolvedVc::cell(export_circuit_breakers),
         }
         .cell())
     }