next.js
f23a96f0 - fix(turbopack): dedupe filename/exportedName in server-reference-manifest worker entries (#95242)

Commit
27 days ago
fix(turbopack): dedupe filename/exportedName in server-reference-manifest worker entries (#95242) ### What Turbopack's `server-reference-manifest` emitter stores `filename` and `exportedName` **twice**: once hoisted to the action-entry level (correct), **and** redundantly inside *every* per-page `ActionManifestWorkerEntry`. This PR removes the per-worker copies so the worker map only carries `{ moduleId, async }` — matching the webpack backend, which already emits them at the entry level only. The emitter even comments the intent (`// Hoist the filename and exported_name to the entry level`) and does the hoist — the per-worker fields are vestigial. ```rust // crates/next-api/src/server_actions.rs entry.workers.insert(&key, ActionManifestWorkerEntry { module_id: loader_id.clone(), is_async: …, exported_name, // ← removed (already on entry) filename: filename.as_ref(), // ← removed (already on entry) }); entry.exported_name = exported_name; // hoist (kept) entry.filename = filename.as_ref(); // hoist (kept) ``` ### Why it matters `filename` and `exportedName` are invariant per action, but the worker map has **one entry per page that can reach the action**, so these strings get repeated `O(actions × pages)` times. On a large App Router app (`output: 'standalone'`, `cacheComponents: true`) this is the dominant cost of the manifest: - ~1,650 routes, **1,847 references → 589,349 `workers` entries** - `server-reference-manifest.json` = **170 MB** / `.js` = **182 MB** (plus the duplicate copy under `.next/standalone`, plus ~314 MB of per-route manifests) - A single `'use cache'`/action reachable from a shared client is registered on **1,638 pages**, each repeating the full `filename` string Dropping the two redundant per-worker fields removes the largest repeated strings from every worker entry, shrinking the manifest substantially with no information loss. ### Why it's safe (consumer-compatible) - The webpack backend already emits worker entries as `{ moduleId, async }` only, with `filename`/`exportedName` at the entry level — so this aligns Turbopack with webpack, not introduces a new shape. - The only first-party reader, `packages/next/src/server/mcp/tools/get-server-action-by-id.ts`, reads `entry.filename` / `entry.exportedName` (entry level) and types `workers` as `Record<string, any>` — it never reads the per-worker copies. ### Changes - `crates/next-core/src/next_manifests/mod.rs` — drop `exported_name` + `filename` from `ActionManifestWorkerEntry` (keep `module_id`, `is_async`). - `crates/next-api/src/server_actions.rs` — stop assigning them in the worker insert (entry-level hoist unchanged). ### Notes - Verified by reading the emitter, the manifest types, and the consumer; I don't have a local Turbopack build, so CI is the source of truth here — if any manifest **snapshot fixtures** include per-worker `filename`/`exportedName`, they'll need regenerating. - Context / measurements: vercel/next.js#95241.
Parents
Loading