Repository navigation
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🚀 Deploying Preview to Cloudflare 🚀Preview Deployments by commit
View all previews: View all previews ↗ |
Codecov Report❌ Patch coverage is
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. 🚀 New features to boost your workflow:
|
|
| 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)
|
Marking as draft because #1157 should be merged first. |
4f2e846 to
dd8f6c1
Compare
dd8f6c1 to
6c45a67
Compare
6c45a67 to
ca2087e
Compare
ca2087e to
b191224
Compare
| * | ||
| * @param {import('hast').Root} tree | ||
| */ | ||
| const groupCodeTabs = tree => |
There was a problem hiding this comment.
I suppose this is for legacy generator and will be removed eventually
| if (tabs.length >= 2) { | ||
| parent.children.splice(index, current - index, { | ||
| type: 'element', | ||
| tagName: 'CodeTabs', | ||
| children: tabs, | ||
| properties: { | ||
| languages: languages.join('|'), | ||
| displayNames: displayNames.join('|'), | ||
| defaultTab, | ||
| }, | ||
| }); |
| const bundler = await resolveBundler(config.bundler); | ||
| const { buildLibraryProgram, buildPageProgram, clientProgram } = | ||
| createProgramBuilder(); |
b191224 to
6ae35a4
Compare
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>
6ae35a4 to
679c997
Compare

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-slimdoc 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:
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.rehype-rawwould 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.htmlrenders 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.createChildProcessin core runs a module in a process of its own and calls it overbirpc(MIT, no dependencies). The Vite adapter runs in one, which ends once the pages are compiled, before they render.jsx-astworkers' heaps (~800MB) are gone while the main thread bundles the site, which is when the build peaked.workerHeapSizeoption,--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.+=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:
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-teardowngives 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, andnode --run format:checkandnode --run lintpass.main, on thewebtarget (71 pages) and on Node core's config (741), exceptall.html, which is unminified, and three C++ pages (addons,embedding,n-api), whose grammar now comes from core's ownshiki(4.4.3) instead of the one@node-core/rehype-shikipins (4.3.1).--max-old-space-size, which limits every isolate, the Node core build passes at 384MB, wheremainneeds 768MB. With--worker-heap-sizealone, 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-shikiis bumped.webtarget, Node 26With one thread, the main thread does all the work, and
workerHeapSizedoesn't apply. Node's--max-old-space-size=768brings 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-shikito bump here)Check List
node --run testand all tests passed.node --run format:check&node --run lint.