Skip to content

perf: reduce build memory and time - #1156

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

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

Conversation

@ovflowd

@ovflowd ovflowd commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Description

Building the Node.js docs peaks at 3.1–3.8GB 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.

This PR brings the peak down to 1.7–2.4GB, and the build time to less than half:

  • 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 docs use about ten languages. Each one is now registered the first time code in it is highlighted, which takes highlighting from ~17s of CPU to ~4s, and the themes go to Shiki by name, so it stops parsing them again for every block. @node-core/rehype-shiki's plugin module still imports every grammar (~33MB and ~0.25s per thread) until nodejs/nodejs.org#9212 is released.
  • 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. 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. Each thread also highlights a type only once: of the ~20,000 types the docs highlight, 741 differ.
  • 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.
  • Idle workers end after 500ms instead of a second, so the jsx-ast workers' heaps (~800MB) are gone while the main thread bundles the site, which is when the build peaked.
  • Worker heaps are limited to 512MB by default (new workerHeapSize option, --worker-heap-size). V8 lets a heap grow to several times its live data before collecting it, four times with the 4GB limit workers get on machines with 16GB+. It costs 3–5% of build time, spent collecting garbage.
  • 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.

How a build runs

The main thread schedules the generators and holds their results. The work goes to a pool of worker threads, and bundling to a child process of its own, each ending once its part is done:

sequenceDiagram
  participant M as Main thread
  participant W as Worker pool (Piscina)
  participant V as Vite child process

  Note over W: up to 4 workers, 512MB of heap each
  M->>W: ast, metadata and jsx-ast chunks
  W-->>M: each page's code, highlighted code as static markup
  Note over W: workers idle for 500ms end
  M->>V: createChildProcess(vite.mjs)
  M->>V: buildServer, buildClient (birpc)
  V-->>M: component library, client assets
  M->>V: compile each page program
  V-->>M: compiled page modules
  M->>V: close()
  Note over V: exits, and Rolldown's memory with it
  M->>W: page tasks, all.html first
  Note over W: import, render, template, minify, write
  W-->>M: written pages
Loading

With threads: 1, the main thread runs the chunks itself.

Note

On Node.js 26, an ended worker's memory stays in the process for the workers started after it rather than going back to the system. Only Node's --no-memory-pool-share-memory-on-teardown gives it back, which code can't set and only Node.js 26 has. The docs mention it as a caveat, but it's probably worth raising upstream with Node.js.

Validation

  • node --run test (759 tests) passes at every commit, and node --run format:check and node --run lint pass.
  • Every page matches main, on the web target (71 pages) and on Node core's config (741), except all.html, which is unminified, and three C++ pages (addons, embedding, n-api), whose grammar now comes from core's own shiki (4.4.3) instead of the one @node-core/rehype-shiki pins (4.3.1).
  • With Node's --max-old-space-size, which limits every isolate, the Node core build passes at 384MB, where main needs 768MB. With --worker-heap-size alone, the workers build every page down to ~350MB.

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. They were measured without the grammar import that nodejs/nodejs.org#9212 removes, so they hold once @node-core/rehype-shiki is bumped.

Build Peak memory Duration CPU time
Node 26, 4 threads 3,491MB → 1,743MB (-50%) 22.9s → 9.8s 86.5s → 42.2s
Node 26, 1 thread 3,595MB → 2,377MB (-34%) 52.3s → 22.2s 64.5s → 28.9s
Node 24, 4 threads 3,811MB → 2,241MB (-41%) 25.8s → 10.3s 93.5s → 45.0s
web target, Node 26 3,131MB → 1,815MB (-42%) 17.7s → 6.8s 58.5s → 29.2s

With one thread, the main thread does all the work, and workerHeapSize doesn't apply. Node's --max-old-space-size=768 brings that build to 1.63GB in the same time.

Related Issues

Refs: #815
Refs: nodejs/node#62045
Depends on nodejs/nodejs.org#9212 (then a release of @node-core/rehype-shiki to bump here)

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 4:32pm 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: Success ✅

View logs ↗
No Preview URL. Enable ↗ 679c997 2026-10-10T16:33:06.552Z View logs ↗
  • Build: Success ✅

View logs ↗
No Preview URL. Enable ↗ 6ae35a4 2026-10-10T16:18:29.109Z View logs ↗
  • Build: Success ✅

View logs ↗
No Preview URL. Enable ↗ b191224 2026-10-10T15:24:19.601Z View logs ↗
  • Build: Failed ❌

View logs ↗
ca2087e 2026-10-10T15:19:46.449Z View logs ↗
  • Build: Failed ❌

View logs ↗
6c45a67 2026-10-10T14:29:53.488Z View logs ↗
  • 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 ↗

View all previews: View all previews ↗

@codecov

codecov Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.34162% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.53%. Comparing base (7238bb1) to head (679c997).

Files with missing lines Patch % Lines
packages/cli/bin/commands/generate.mjs 0.00% 7 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.53%   +0.21%     
==========================================
  Files         273      282       +9     
  Lines       27050    28057    +1007     
  Branches     2722     2815      +93     
==========================================
+ Hits        25244    26244    +1000     
- Misses       1784     1791       +7     
  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

Performance estimate (single CI run)

  • Generation time: 5.0% slower (1.40 s → 1.47 s)
  • Peak memory: 3.7% lower (434.64 MB → 418.58 MB)

json Generator

Performance estimate (single CI run)

  • Generation time: 13.5% slower (9.01 s → 10.23 s)
  • Peak memory: 16.8% lower (1.49 GB → 1.24 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: 53.6% faster (39.43 s → 18.29 s)
  • Peak memory: 27.9% lower (2.48 GB → 1.79 GB)

legacy-json Generator

Performance estimate (single CI run)

  • Generation time: 16.4% slower (8.36 s → 9.73 s)
  • Peak memory: 25.7% lower (1.57 GB → 1.17 GB)

llms-txt Generator

Performance estimate (single CI run)

  • Generation time: 70.6% slower (4.87 s → 8.31 s)
  • Peak memory: 31.3% lower (1.77 GB → 1.22 GB)

orama-db Generator

Output size: 1 file changed · net -121.00 B

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

Performance estimate (single CI run)

  • Generation time: 58.0% slower (5.52 s → 8.72 s)
  • Peak memory: 23.3% lower (1.84 GB → 1.41 GB)

web Generator

Output size: 68 files changed · net +1.82 MB

File size details
File Main PR Change
all.html 33.10 MB 34.92 MB +1.82 MB (+5.5%)
assets/SearchBox-wQQHMZuJ.js 84.67 KB — -84.67 KB (-100.0%)
assets/SearchBox-DjNnuz7W.js — 84.67 KB +84.67 KB
assets/dist-D5dOZyAD.js 30.42 KB — -30.42 KB (-100.0%)
assets/dist-DruvUqXN.js — 30.42 KB +30.42 KB
assets/SideBar-CjBhW9Hu.js 25.62 KB — -25.62 KB (-100.0%)
assets/SideBar-DaKBoTPQ.js — 25.62 KB +25.62 KB
assets/client-eAdjoyYV.js 24.91 KB — -24.91 KB (-100.0%)
assets/client-DxC3O6Ca.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-0Ghc6FKv.js 16.11 KB — -16.11 KB (-100.0%)
assets/Combination-d5oRXla-.js — 16.11 KB +16.11 KB
assets/ThemeToggle-BZ0_KYDS.js 13.65 KB — -13.65 KB (-100.0%)
assets/ThemeToggle-DVZdvZ7l.js — 13.65 KB +13.65 KB
assets/dist-5O-XzCME.js 10.20 KB — -10.20 KB (-100.0%)
assets/dist-DVEbYasF.js — 10.20 KB +10.20 KB
assets/Layout-N2aBwWVg.js 10.13 KB — -10.13 KB (-100.0%)
assets/Layout-C5yPmBoc.js — 10.13 KB +10.13 KB
assets/compat-DmJKK7tl.js 10.13 KB — -10.13 KB (-100.0%)
assets/compat-ByVaDh64.js — 10.13 KB +10.13 KB
assets/Tooltip-_V0pbK8z.js 7.93 KB — -7.93 KB (-100.0%)
assets/Tooltip-C41LEB8U.js — 7.93 KB +7.93 KB
assets/dist-CX7YxNak.js 6.99 KB — -6.99 KB (-100.0%)
assets/dist-DNbUuC3x.js — 6.99 KB +6.99 KB
assets/jsx-runtime-BXRXL30K.js 5.67 KB — -5.67 KB (-100.0%)
assets/jsx-runtime-CW012A50.js — 5.67 KB +5.67 KB
assets/dist-D7-8NdKu.js 3.93 KB — -3.93 KB (-100.0%)
assets/dist-cDFsq_2f.js — 3.93 KB +3.93 KB
assets/CodeTabs-BCN1ZuaS.js 3.89 KB — -3.89 KB (-100.0%)
assets/CodeTabs-DT17uorw.js — 3.89 KB +3.89 KB
assets/CodeBox-BU9A6SQ0.js 3.44 KB — -3.44 KB (-100.0%)
assets/CodeBox-BNILFdqT.js — 3.44 KB +3.44 KB
assets/hooks.module-DmVOR8gj.js 3.40 KB — -3.40 KB (-100.0%)
assets/hooks.module-BbOQEtt2.js — 3.40 KB +3.40 KB
assets/FunctionSignature-BkGQ3GtN.js 2.28 KB — -2.28 KB (-100.0%)
assets/FunctionSignature-BQoQjJ6P.js — 2.28 KB +2.28 KB
assets/Banner-DmiHcrIG.js 2.13 KB — -2.13 KB (-100.0%)
assets/Banner-Zf4myBWm.js — 2.13 KB +2.13 KB
assets/ChangeHistory-Py08-aa6.js 1.77 KB — -1.77 KB (-100.0%)
assets/ChangeHistory-C3DUcRCX.js — 1.77 KB +1.77 KB
assets/DataTag-8MiOm8lc.js 856.00 B — -856.00 B (-100.0%)
assets/DataTag-CTUSGgLj.js — 856.00 B +856.00 B
assets/DocumentationIndex-C41jjOOT.js 833.00 B — -833.00 B (-100.0%)
assets/DocumentationIndex-D_jFywVL.js — 833.00 B +833.00 B
addons.html 377.82 KB 377.09 KB -751.00 B (-0.2%)
assets/ArrowUpRightIcon-CeI6pEnL.js 618.00 B — -618.00 B (-100.0%)
assets/ArrowUpRightIcon-BSpTwAaB.js — 618.00 B +618.00 B
assets/Badge-Cd_pO_Vx.js 616.00 B — -616.00 B (-100.0%)
assets/Badge-CZF-RiB7.js — 616.00 B +616.00 B
assets/AlertBox-rhpuVHCX.js 591.00 B — -591.00 B (-100.0%)
assets/AlertBox-BNnsWt2b.js — 591.00 B +591.00 B
assets/CodeBracketIcon-NvhDSqX7.js 512.00 B — -512.00 B (-100.0%)
assets/CodeBracketIcon-TvXI-m3T.js — 512.00 B +512.00 B
assets/dist-BDAKpzg5.js 477.00 B — -477.00 B (-100.0%)
assets/dist-D1kGHM6c.js — 477.00 B +477.00 B
assets/ChevronDownIcon-CTgBO3tH.js 468.00 B — -468.00 B (-100.0%)
assets/ChevronDownIcon-BOnMJQ3A.js — 468.00 B +468.00 B
assets/renderLabel-_7gv3mHW.js 447.00 B — -447.00 B (-100.0%)
assets/renderLabel-CdEZo9LC.js — 447.00 B +447.00 B
assets/Blockquote-D4eEaruL.js 167.00 B — -167.00 B (-100.0%)
assets/Blockquote-C7yOM0SI.js — 167.00 B +167.00 B
assets/useRemoteConfig-BPIYr4xA.js 151.00 B — -151.00 B (-100.0%)
assets/useRemoteConfig-DRPTLZY3.js — 151.00 B +151.00 B
n-api.html 1.00 MB 1.00 MB +113.00 B (+0.0%)
assets/withIsland-BKNTtLQ7.js 105.00 B — -105.00 B (-100.0%)
assets/withIsland-C7ctRfG5.js — 105.00 B +105.00 B
embedding.html 69.31 KB 69.27 KB -41.00 B (-0.1%)

Performance estimate (single CI run)

  • Generation time: 41.7% faster (46.98 s → 27.37 s)
  • Peak memory: 45.8% lower (3.72 GB → 2.02 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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

*
* @param {import('hast').Root} tree
*/
const groupCodeTabs = tree =>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose this is for legacy generator and will be removed eventually

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The rewritten code-tab plugin drops groupId behavior, and child startup can leak a process when program setup fails.

2 open findings
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

🧠 Review effort: Balanced

Comment on lines +89 to +99
if (tabs.length >= 2) {
parent.children.splice(index, current - index, {
type: 'element',
tagName: 'CodeTabs',
children: tabs,
properties: {
languages: languages.join('|'),
displayNames: displayNames.join('|'),
defaultTab,
},
});
Comment on lines 42 to 44
const bundler = await resolveBundler(config.bundler);
const { buildLibraryProgram, buildPageProgram, clientProgram } =
createProgramBuilder();
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, without
importing `LANGS`. The themes are given to Shiki by name, which it keeps
parsed instead of parsing them for every highlight. Importing
`@node-core/rehype-shiki`'s plugin still imports every grammar until
nodejs/nodejs.org#9212 is released.

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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
A worker that highlights code no longer registers 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 <noreply@anthropic.com>
V8 lets a heap grow to several times its live data before collecting
it, the more the higher its limit: four times from 2GB up, and on a
machine with plenty of memory every worker's limit is 4GB, so each held
several times what it was using.

`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` (512): a
machine with less memory keeps the smaller limit V8 picks for it. The
biggest page of the Node.js docs, `all.html`, takes ~300MB. An explicit
`--max-old-space-size` still wins, as V8 prefers it, and it's what
limits the main thread, which runs the generators with one thread.

On Node core's build with 4 threads, the peak goes from 2.02GB to
1.67GB on Node 26, and from 2.44GB to 2.19GB on Node 24, for 3-5% more
time spent collecting garbage. The build passes with workers limited to
as little as ~350MB.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
A worker only ended a second after its last task, so the `jsx-ast`
workers and their heaps (~800MB on the Node.js docs) were still there
while the main thread bundled the site, which is when the build peaked.
They now end half a second after running out of work: soon enough to be
gone while the site is bundled, but not in the short gaps between two
generators, where ending and starting workers again costs time.

On Node core's build with 4 threads on Node 26, the peak goes from
~2.31GB to ~1.91GB.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Of the ~20,000 types the Node.js docs highlight, 741 are different:
`{string}` alone is on most pages. Each thread now keeps the markup of
the types it highlighted, by highlighter, so a type it has seen is only
embedded again, saving ~4% of the build's time and ~5% of its CPU.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
On Node.js 26, an ended worker's heap pages stay in the process for the
workers started after it, so a build holds them while the main thread
bundles the site (~800MB on the Node.js docs). Only Node's
`--no-memory-pool-share-memory-on-teardown` gives them back:
`v8.setFlagsFromString` has no effect on it, and `NODE_OPTIONS` doesn't
accept it. Node.js 24 gives the memory back by itself after a few
seconds, and rejects the flag.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview – api-docs-tooling — 679c997d 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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants