next.js
7d19c34e - Turbopack: write smaller/faster client reference manifests (#101312)

Commit
13 hours ago
Turbopack: write smaller/faster client reference manifests (#101312) ## Summary Turbopack's `*_client-reference-manifest.js` files repeat the same chunk lists for every client reference: each `clientModules`, `ssrModuleMapping`, and `rscModuleMapping` entry of a page lists all the chunks of its chunk group, so a manifest is mostly copies of a handful of lists. On a large app the manifests totaled **1.67 GiB** on disk, were the single largest retained allocation site in the build (~1.7 GiB of the live heap at the end of `next build`), and took ~6 s to evaluate at runtime (`eval` in node). This writes the manifest as a JS expression that evaluates to the same object, but: - writes each distinct chunk path once, in a string table `s`, with directory prefixes that repeat shared as constants (`p0+"abc.js"`) - writes each chunk list that is used more than once once, in a table `l` - encodes lists as runs of consecutive string indices, `r(start,length,...)`, since paths are numbered in first-seen order - builds entries with factories, `e(id,chunks)` / `n(id,chunks)`, instead of repeating `{"id":..,"name":"*","chunks":..,"async":false}` - does the same for `entryJSFiles` and `entryCSSFiles` (inlined CSS contents go in the string table too, without prefix splitting) ```js (() => { const p0 = "/_next/static/chunks/", p1 = "server/chunks/ssr/", p2 = "static/chunks/"; const s = [p0+"…js", …]; const r = (...a) => { /* s[a[0]..a[0]+a[1]], … */ }; const l = [r(0,46), …]; const e = (id, chunks, async = false) => ({ id, name: "*", chunks, async }), n = …; (globalThis.__RSC_MANIFEST ??= {})["/page"] = {"moduleLoading":…,"clientModules":{"[project]/…":e(2501066,l[0]),…},"ssrModuleMapping":{"2501066":n(5564085,l[3]),…},…}; })(); ``` The tables are built while the manifest is built (`ChunkTables` in `next_manifests/chunk_tables.rs`) instead of serializing to JSON first. Lists that occur several times now evaluate to the same array object; nothing mutates them (the runtime deployment-id suffix maps them into new arrays). Other changes: - `next internal static-routes-info` `require()`s the manifest and reads the global instead of extracting a JSON body by hand, since the body is no longer JSON. - Chunk URLs are percent-encoded with `encode_uri_path`, which writes into the URL buffer directly and does not allocate when nothing needs escaping. ### Results (a large app, `next build --experimental-build-mode=compile`, cold) | | before | after | | --- | --- | --- | | Client reference manifests on disk (939 files) | 1674.7 MiB | 34.0 MiB | | Evaluating all manifests (`node` 24, `runInThisContext`) | ~6.0 s | ~0.5 s | | Heap retained by the loaded manifests | 313 MiB | 121 MiB | | Live heap at the end of the build (sampled) | 14.67 GiB | 12.91 GiB | | Build time | 79.4 s | 79.0 s | Fewer array and object literals also make the scripts faster to parse and compile in V8, which is why evaluation improves more than size alone would suggest. --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Author
Parents
Loading