Skip to content

perf: reduce build memory and time (PoC) - #1156

Draft
ovflowd wants to merge 11 commits into
mainfrom
perf/memory-usage
Draft

ovflowd wants to merge 11 commits into
mainfrom
perf/memory-usage

Conversation

@ovflowd

@ovflowd ovflowd commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Description

Important

This is a proof of concept, opened as a draft to discuss the approach. Happy to split it into smaller PRs once we agree on the direction.

Building the Node.js docs peaks at 3.7–3.9GB of memory, while a single page only needs ~100MB. Node core only builds the HTML docs on machines with more than 5GiB of memory, and silently skips them otherwise. That's what happens on its ubuntu-slim doc CI, whose docs artifact has no HTML at all, and most likely on the release machine too.

This PR brings the peak down to 2.1–2.7GB, and the build time to less than half, by tackling where that memory went:

  • Shiki registers grammars on demand. Once a thread highlighted code, the Shiki plugin registered all ~250 grammars Shiki bundles, and importing it imported all of them on every thread loading jsx-ast, the main thread included. The docs use about ten languages. Each language is now registered the first time code in it is highlighted, which takes highlighting from ~17s of CPU to ~4s. The plugin is a port of @node-core/rehype-shiki's, whose module imports every grammar, and the themes go to Shiki by name, so it stops parsing them again for every block.
  • Highlighted code reaches pages as static markup. Every highlighted token became a hast node, then a JSX element, then generated code, only to be rendered back into the same HTML: most of what jsx-ast allocated. A plugin after Shiki embeds the code blocks, and types and signatures are embedded as they're highlighted, before rehype-raw would parse each of their tokens again.
  • all.html renders in the worker pool, unminified. It was rendered and minified on the main thread (~1GB), and the minifier's WASM memory grows to ~12× the page without ever shrinking. Unminified, it's 5% larger, or ~2% once gzipped.
  • Vite runs in a child process. Rolldown only returns its native memory (~250MB here) when its process exits. The new createChildProcess in core runs a module in a process of its own and calls it over birpc (MIT, no dependencies). The Vite adapter runs in one, which ends once the pages are compiled, before they render.
  • Worker heaps are limited to V8's default, capped at 2047MB (new workerHeapSize option, --worker-heap-size). With a limit of 2GB or more (4GB on machines with 16GB+), V8 lets a heap grow to 4× its live data before collecting it; just under 2GB, about 2×.
  • Smaller fixes: the generated code and rendered HTML are flattened once complete (strings built with += held ~10× their text), and each thread only imports the modules it uses.

Validation

  • node --run test (761 tests), node --run format:check and node --run lint pass. New tests cover loading grammars on demand, the static markup, the child process (including one that dies mid-call) and the workers' heap limit.
  • Every page matches main, on the web target (71 pages) and on Node core's config (741), except all.html and three C++ pages (addons, embedding, n-api). all.html is unminified, and minifying it gives main's page back outside those C++ code blocks. Their highlighting changes because grammars now come from core's own shiki (4.4.3) instead of the one @node-core/rehype-shiki pins (4.3.1). Pinning core's to ~4.3.1 would keep the old highlighting.

Benchmarks

main → this branch, building Node core's docs with its config (legacy-json-all + section-pages) unless noted. Medians of 3 interleaved runs on an M4 Pro; peak memory is the physical footprint of every process in the build.

Build Peak memory Duration CPU time
Node 26, 4 threads 3,689MB → 2,327MB (-37%) 22.5s → 9.3s 83.3s → 41.0s
Node 26, 1 thread 3,925MB → 2,203MB (-44%) 50.1s → 21.9s 61.7s → 29.2s
Node 24, 4 threads 3,841MB → 2,675MB (-30%) 23.6s → 10.0s 91.9s → 43.7s
web target, Node 26 3,258MB → 2,144MB (-34%) 17.8s → 6.6s 58.0s → 27.3s

CI's per-generator comment compares a single run with a single run of main on another runner, which swings ±20% even on unrelated PRs (e.g. #1155).

Related Issues

Refs: #815
Refs: nodejs/node#62045

Check List

  • I have read the Contributing Guidelines and made commit messages that follow the guideline.
  • I have run node --run test and all tests passed.
  • I have check code formatting with node --run format:check & node --run lint.
  • I've covered new added functionality with unit tests if necessary.

@vercel

vercel Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
api-docs-tooling Ready Ready Preview Oct 10, 2026 11:59am UTC

Request Review

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Failed ❌

View logs ↗
dd8f6c1 2026-10-10T11:59:51.605Z View logs ↗
  • Build: Failed ❌

View logs ↗
4f2e846 2026-10-08T17:07:36.252Z View logs ↗
  • Build: Failed ❌

View logs ↗
d41d989 2026-10-08T15:54:02.554Z View logs ↗
  • Build: Failed ❌

View logs ↗
e66eea8 2026-10-08T13:09:12.156Z View logs ↗
  • Build: Failed ❌

View logs ↗
e63c654 2026-10-08T12:16:56.748Z View logs ↗
  • Build: Failed ❌

View logs ↗
8c433b8 2026-10-08T11:14:18.569Z View logs ↗
  • Build: Failed ❌

View logs ↗
d2facfb 2026-10-07T21:49:54.750Z View logs ↗

@codecov

codecov Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.19732% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.55%. Comparing base (de88b03) to head (dd8f6c1).

Files with missing lines Patch % Lines
packages/cli/bin/commands/generate.mjs 0.00% 7 Missing ⚠️
packages/core/src/plugins/shiki/rehype.mjs 98.03% 3 Missing ⚠️
...ages/node-legacy/src/legacy-html/plugins/shiki.mjs 75.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1156      +/-   ##
==========================================
+ Coverage   93.32%   93.55%   +0.23%     
==========================================
  Files         273      282       +9     
  Lines       27050    28171    +1121     
  Branches     2722     2836     +114     
==========================================
+ Hits        25244    26356    +1112     
- Misses       1784     1793       +9     
  Partials       22       22              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

api-links Generator

Output: 1 file differs

apilinks.json
Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines

  {
    'Agent.defaultMaxSockets': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L300',
    'Buffer.alloc': 'https://github.com/{repository}/blob/HEAD/lib/buffer.js#L454',
    'Buffer.allocUnsafe': 'https://github.com/{repository}/blob/HEAD/lib/buffer.js#L473',
    'Buffer.allocUnsafeSlow': 'https://github.com/{repository}/blob/HEAD/lib/buffer.js#L496',
...
    'agent.addRequest': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L365',
+   'agent.createConnection': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L304',
-   'agent.createConnection': 'https://github.com/{repository}/blob/HEAD/lib/https.js#L354',
    'agent.createSocket': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L446',
    'agent.destroy': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L679',
+   'agent.getName': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L334',
+   'agent.keepSocketAlive': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L635',
-   'agent.getName': 'https://github.com/{repository}/blob/HEAD/lib/https.js#L537',
-   'agent.keepSocketAlive': 'https://github.com/{repository}/blob/HEAD/lib/https.js#L506',
    'agent.removeSocket': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L574',
    'agent.reuseSocket': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L671',
    'assert.assert': 'https://github.com/{repository}/blob/HEAD/lib/assert.js#L185',
    'asyncResource.asyncId': 'https://github.com/{repository}/blob/HEAD/lib/async_hooks.js#L243',
    'asyncResource.bind': 'https://github.com/{repository}/blob/HEAD/lib/async_hooks.js#L275',
...
    'server.address': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2885',
+   'server.close': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L697',
+   'server.closeAllConnections': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L707',
+   'server.closeIdleConnections': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L719',
-   'server.close': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L3018',
-   'server.closeAllConnections': 'https://github.com/{repository}/blob/HEAD/lib/https.js#L125',
-   'server.closeIdleConnections': 'https://github.com/{repository}/blob/HEAD/lib/https.js#L127',
    'server.getConnections': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2980',
    'server.listen': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2675',
    'server.ref': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L3132',
+   'server.setTimeout': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L735',
-   'server.setTimeout': 'https://github.com/{repository}/blob/HEAD/lib/https.js#L129',
    'server.unref': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L3141',
+   'server[SymbolAsyncDispose]': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L703',
-   'server[SymbolAsyncDispose]': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L3059',
    'server[SymbolAsyncIterator]': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L3066',
    'server[kDeserialize]': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2596',
    'server[kTransferList]': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2568',
    'server[kTransfer]': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2573',
+   'server[undefined]': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L742',
-   'server[undefined]': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L3096',
    'serverresponse._finish': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L262',
    'serverresponse._implicitHeader': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L419',
    'serverresponse.assignSocket': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L312',
    'serverresponse.detachSocket': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L323',
    'serverresponse.statusCode': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L285',

Performance estimate (single CI run)

  • Generation time: 15.6% slower (1.28 s → 1.48 s)
  • Peak memory: 3.6% higher (426.40 MB → 441.77 MB)

json Generator

Performance estimate (single CI run)

  • Generation time: 7.7% faster (10.21 s → 9.42 s)
  • Peak memory: 16.2% lower (1.62 GB → 1.36 GB)

legacy-html Generator

Output size: 2 files changed · net +8.24 KB

File size details
File Main PR Change
addons.html 354.63 KB 361.27 KB +6.64 KB (+1.9%)
embedding.html 59.79 KB 61.38 KB +1.59 KB (+2.7%)

Performance estimate (single CI run)

  • Generation time: 43.5% faster (30.15 s → 17.04 s)
  • Peak memory: 25.5% lower (2.50 GB → 1.86 GB)

legacy-json Generator

Performance estimate (single CI run)

  • Generation time: 2.8% faster (8.70 s → 8.46 s)
  • Peak memory: 19.0% lower (1.64 GB → 1.33 GB)

llms-txt Generator

Performance estimate (single CI run)

  • Generation time: 27.6% slower (4.67 s → 5.96 s)
  • Peak memory: 16.5% lower (1.64 GB → 1.37 GB)

orama-db Generator

Output size: 1 file changed · net -2.00 B

File size details
File Main PR Change
orama-db.json 9.57 MB 9.57 MB -2.00 B (-0.0%)

Performance estimate (single CI run)

  • Generation time: 76.2% slower (4.92 s → 8.67 s)
  • Peak memory: 23.2% lower (1.87 GB → 1.43 GB)

web Generator

Output size: 68 files changed · net +1.82 MB

File size details
File Main PR Change
all.html 33.07 MB 34.89 MB +1.82 MB (+5.5%)
assets/SearchBox-886q7CtQ.js 84.66 KB — -84.66 KB (-100.0%)
assets/SearchBox-ClnW7TK8.js — 84.66 KB +84.66 KB
assets/dist-CKngGbBr.js 30.42 KB — -30.42 KB (-100.0%)
assets/dist-DaJ62IKD.js — 30.42 KB +30.42 KB
assets/SideBar-BZ0sUX8x.js 25.61 KB — -25.61 KB (-100.0%)
assets/SideBar-BHV36Nzp.js — 25.61 KB +25.61 KB
assets/client-BXtnUTPQ.js 24.91 KB — -24.91 KB (-100.0%)
assets/client-B3VvMd8I.js — 24.91 KB +24.91 KB
assets/config-Cx-IX3nR.js 24.37 KB — -24.37 KB (-100.0%)
assets/config-DtT2jlD0.js — 24.09 KB +24.09 KB
assets/Combination-OVHFiUe7.js 16.11 KB — -16.11 KB (-100.0%)
assets/Combination-BXjTcf0a.js — 16.11 KB +16.11 KB
assets/ThemeToggle-D_UY-0YN.js 13.65 KB — -13.65 KB (-100.0%)
assets/ThemeToggle-BDk05zng.js — 13.65 KB +13.65 KB
assets/dist-C6BUFa3H.js 10.20 KB — -10.20 KB (-100.0%)
assets/dist-DAVnU38z.js — 10.20 KB +10.20 KB
assets/Layout-C6XLgpZ3.js 10.13 KB — -10.13 KB (-100.0%)
assets/Layout-R7RUVoMG.js — 10.13 KB +10.13 KB
assets/compat-D_gF0q9c.js 10.13 KB — -10.13 KB (-100.0%)
assets/compat-BdLDmi5I.js — 10.13 KB +10.13 KB
assets/Tooltip-BCBLALa2.js 7.93 KB — -7.93 KB (-100.0%)
assets/Tooltip-DhSa3-SO.js — 7.93 KB +7.93 KB
assets/dist-CEw6Vg07.js 6.99 KB — -6.99 KB (-100.0%)
assets/dist-EwcSEbA2.js — 6.99 KB +6.99 KB
assets/jsx-runtime-nvJAqap8.js 5.67 KB — -5.67 KB (-100.0%)
assets/jsx-runtime-B2kvX5Dd.js — 5.67 KB +5.67 KB
assets/dist-BH7R-Ve2.js 3.93 KB — -3.93 KB (-100.0%)
assets/dist-_cK0Q299.js — 3.93 KB +3.93 KB
assets/CodeTabs-BtSs7AO4.js 3.89 KB — -3.89 KB (-100.0%)
assets/CodeTabs-CxesZulN.js — 3.89 KB +3.89 KB
assets/CodeBox-CFjkd_W5.js 3.44 KB — -3.44 KB (-100.0%)
assets/CodeBox-BN6z2RpZ.js — 3.44 KB +3.44 KB
assets/hooks.module-CW9C37Hs.js 3.40 KB — -3.40 KB (-100.0%)
assets/hooks.module-DV8BJgCx.js — 3.40 KB +3.40 KB
assets/FunctionSignature-CW3k6SWp.js 2.28 KB — -2.28 KB (-100.0%)
assets/FunctionSignature-BHfSHcI7.js — 2.28 KB +2.28 KB
assets/Banner-C05n5eub.js 2.13 KB — -2.13 KB (-100.0%)
assets/Banner-CFOw4ClD.js — 2.13 KB +2.13 KB
assets/ChangeHistory-7LT_-gdw.js 1.77 KB — -1.77 KB (-100.0%)
assets/ChangeHistory-Dyc9FvW-.js — 1.77 KB +1.77 KB
assets/DataTag-zNRK6tTk.js 844.00 B — -844.00 B (-100.0%)
assets/DataTag-DUkSX3Tx.js — 844.00 B +844.00 B
assets/DocumentationIndex-C3DhS3Pm.js 833.00 B — -833.00 B (-100.0%)
assets/DocumentationIndex-DM3ImrxD.js — 833.00 B +833.00 B
addons.html 377.69 KB 376.96 KB -751.00 B (-0.2%)
assets/ArrowUpRightIcon-Bi08b7Qh.js 618.00 B — -618.00 B (-100.0%)
assets/ArrowUpRightIcon-BaLMheMJ.js — 618.00 B +618.00 B
assets/Badge-BR0j23fd.js 607.00 B — -607.00 B (-100.0%)
assets/Badge-oAi6J1ZB.js — 607.00 B +607.00 B
assets/AlertBox-BNTUtRYe.js 591.00 B — -591.00 B (-100.0%)
assets/AlertBox-B0FVXSf0.js — 591.00 B +591.00 B
assets/CodeBracketIcon-D9zS3xWH.js 512.00 B — -512.00 B (-100.0%)
assets/CodeBracketIcon-Da62_n8V.js — 512.00 B +512.00 B
assets/dist-DHGxzEgh.js 477.00 B — -477.00 B (-100.0%)
assets/dist-Dw05tQo8.js — 477.00 B +477.00 B
assets/ChevronDownIcon-DY_kMtyw.js 468.00 B — -468.00 B (-100.0%)
assets/ChevronDownIcon-Bv3nn1RV.js — 468.00 B +468.00 B
assets/renderLabel-B05hHKeG.js 447.00 B — -447.00 B (-100.0%)
assets/renderLabel-MrIJcOxn.js — 447.00 B +447.00 B
assets/Blockquote-CxiKUNKo.js 167.00 B — -167.00 B (-100.0%)
assets/Blockquote-DsF6xEUn.js — 167.00 B +167.00 B
assets/useRemoteConfig-DGOFaw-H.js 151.00 B — -151.00 B (-100.0%)
assets/useRemoteConfig-Du1lHNBi.js — 151.00 B +151.00 B
n-api.html 1022.93 KB 1023.04 KB +113.00 B (+0.0%)
assets/withIsland-C8b6-JCe.js 105.00 B — -105.00 B (-100.0%)
assets/withIsland-DZPeGbXA.js — 105.00 B +105.00 B
embedding.html 69.16 KB 69.12 KB -41.00 B (-0.1%)

Performance estimate (single CI run)

  • Generation time: 64.4% faster (47.31 s → 16.84 s)
  • Peak memory: 33.8% lower (3.31 GB → 2.19 GB)

Comment thread packages/core/src/threading/services.mjs Outdated
Comment thread packages/core/src/threading/remote-host.mjs Outdated
@ovflowd

ovflowd commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

Marking as draft because #1157 should be merged first.

The Shiki plugin registered every bundled language (~250 grammars) in
each thread that highlighted code, which cost ~2s and ~100MB per thread
and made every highlight several times slower, as each code block was
matched against grammars it never uses. Importing it also imported all
of them, through `@node-core/rehype-shiki`'s `LANGS` and its plugin,
on every thread loading `jsx-ast`, the main thread included.

The highlighter now registers a bundled language the first time code in
it is highlighted, along with the bundled languages a configured one
embeds, and lists the bundled ones from their metadata alone. The rehype
plugin is a port of `@node-core/rehype-shiki`'s, so importing it no
longer imports every grammar, and the themes are given to Shiki by
name, which it keeps parsed instead of parsing them for every highlight.

The grammars now come from doc-kit's own `shiki` dependency (4.4.3)
rather than the copy `@node-core/rehype-shiki` pins (4.3.1). Its C++
grammar highlights types and template arguments differently, which
shows on the Node.js docs' C++ examples.

Assisted-by: Claude Opus 5.5 <[email protected]>
Every highlighted token of every code block, signature and type became
a hast element, then a JSX element, then generated code, only to be
rendered back into the same markup: most of what `jsx-ast` allocated,
and much of what the pages' code took to compile and render. Highlighted
code is static, as islands adopt it without rendering it again, so it
now reaches the pages as the markup Preact renders it to, held by a
`<code>` through `dangerouslySetInnerHTML`.

Code blocks are embedded by a plugin running after Shiki. Types and
signatures are embedded as they are highlighted, before `rehype-raw`,
which would otherwise parse each of their tokens again.

Assisted-by: Claude Opus 5.5 <[email protected]>
The pool renders a single task on the calling thread, so `all.html`,
rendered on its own after the other pages, was rendered on the main
thread. That held the whole site in its heap, and kept the pool from
shutting its idle workers down until it was done. It is now rendered
alongside the other pages, first, as it takes by far the longest.

It is no longer minified either. The minifier's memory grows to about
twelve times the page it is given and is never returned, and this page
is the whole site: minifying the Node.js docs' ~35MB `all.html` takes
~400MB and over a second, for a page 2% smaller once compressed.

Assisted-by: Claude Opus 5.5 <[email protected]>
V8 keeps a string built by concatenation as a rope of all of its pieces,
so the code `jsx-ast` generates (one write per token) and the HTML
Preact renders (one write per tag) took around ten times the size of
their text: 28MB of page code was held as 363MB, and `all.html` alone as
~430MB. Both are now flattened as soon as they are complete, which lets
the pieces be collected.

Assisted-by: Claude Opus 5.5 <[email protected]>
The main thread loads every generator of a run, but only ever calls
`generate`; building the pages is the workers' job. Still, the
generators imported what only their workers use, so the main thread
loaded the TypeScript parser and the HTML minifier (both WASM), and
what building a page takes. Those are now imported where they are used.

`getFullName` also moves to a module of its own: `buildBarProps`, which
`section-pages` imports, and `buildContent` only need a page's full
name, not the code that builds and highlights signatures.

Assisted-by: Claude Opus 5.5 <[email protected]>
The threads rendering pages load the `html` generator's module, and so
everything `generate` imports, along with `processing.mjs`. That
included `config.mjs`, which loads Shiki for the languages' display
names and a Markdown processor of its own: ~14MB and ~75ms per worker,
for what only the main thread uses, while the workers render pages,
which is when the build peaks.

`createVirtualImports` moves into `config.mjs`, which `generate` imports
when it bundles the site, so the main thread alone loads it.

Assisted-by: Claude Opus 5.5 <[email protected]>
A process gives all of its memory back when it exits, native memory
included, which a worker thread does not: Rolldown, for one, only frees
its allocator's memory with its process. `createChildProcess(moduleURL)`
runs a module in a child process of its own through a generic host
script, and calls its exports over birpc (MIT, no dependencies). Calls
in flight fail once the process exits, and `close` ends it.

`on` returns nothing: birpc holds its first call until whatever `on`
returns settles, so returning the emitter let a `close` right after a
call end the process before the call was written, failing it with EPIPE
rather than with the process's exit.

Assisted-by: Claude Opus 5.5 <[email protected]>
Vite bundles with Rolldown, whose native memory (~250MB building the
Node.js docs) is only returned when its process exits, so it stayed in
the build's process while the pages rendered, which is when the build
peaks. The default Vite adapter now runs in a child process of its own
(`createChildProcess`), which `generate` ends once every page program is
compiled, before the pages are rendered.

Bundlers can have a `close` for that: `generate` calls it once it is
done bundling and compiling. An adapter passed as `bundler` still runs
on the main thread, so its function-valued options keep working.

Assisted-by: Claude Opus 5.5 <[email protected]>
A worker that highlights code no longer holds every grammar Shiki
bundles, only those of the code it highlighted. The comment now says
what holds for every generator: each worker has a heap of its own, with
the libraries and the pages it is working on.

Assisted-by: Claude Opus 5.5 <[email protected]>
V8 lets a heap whose limit is 2GB or more grow to up to four times its
live data before collecting it, and on a machine with plenty of memory
every worker's limit is 4GB, so each held several times what it used.
Below 2GB, a heap only grows to about twice its live data.

`workerHeapSize` (`--worker-heap-size`) sets each worker's old space
limit, in MB, like `threads` sets their number. It defaults to the limit
V8 gives this process, at most `DEFAULT_MAX_WORKER_HEAP_SIZE` (2047): a
machine with less memory keeps the smaller limit V8 picks for it. An
explicit `--max-old-space-size` still wins, as V8 prefers it.

On Node core's build (4 threads), the peak goes from 2.73GB to 2.39GB on
Node 26 and from 2.90GB to 2.73GB on Node 24. Lower limits gain little
more: 2.29GB at 1536MB, 2.30GB at 768MB.

Assisted-by: Claude Opus 5.5 <[email protected]>

This branch was successfully deployed

1 active deployment
Preview – api-docs-tooling — dd8f6c18 Deployed Oct 10, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants