Skip to content

refactor: move all.html into a Node.js generator - #1242

Open
ovflowd wants to merge 2 commits into
perf/stream-htmlfrom
refactor/html-all
Open

ovflowd wants to merge 2 commits into
perf/stream-htmlfrom
refactor/html-all

Conversation

@ovflowd

@ovflowd ovflowd commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Description

This PR moves all.html, every module page's content on one page, out of html and into a new generator of @node-core/doc-kit, html-all. It's the sixth part of a stack that splits #1205.

It's Node.js's, so doc-kit itself shouldn't need to know about it, but html built it in behind generateAllPage, and the streaming work spread its bookkeeping through generate.mjs.

  • html-all is spliced between jsx-ast and html, the way section-pages is between metadata and jsx-ast. It passes the pages on, adds all.html's metadata to the manifest, and right after the last module page adds all.html as a composed page of them, in sidebar order. jsx-ast always sends the manifest, and every page it lists arrives, so there's no fallback for a run without one.
  • html only knows that a composed page, an item with parts, the pages before it whose content it shows, is compiled from their content and rendered first, one part at a time instead of building every part's elements before rendering any. On Node.js's docs the worker rendering all.html went from 330-388MB to 176-189MB live.
  • generateAllPage is removed, which is a breaking change for @doc-kit/generator-react. Builds that want all.html add the html-all target, as CI's e2e build now does (-t web -t html-all). Node.js's config (doc: move to redesign node#66713) needs it in target too.

Why it's built this way

Why a generator instead of the generateAllPage option? all.html is Node.js's page, but generateAllPage defaulted to true, so every site built with @doc-kit/generator-react got one, doc-kit's own site included (www/doc-kit.config.mjs targets html). As a generator it sits in @node-core/doc-kit next to man-page and api-links, and utils/all.mjs goes. Counting code only, without tests and docs, it's +173/-109.

Why not keep generateAllPage as a deprecated alias? html can't import html-all, because @node-core/doc-kit now depends on @doc-kit/generator-react. The alias would have to live in core and turn the option into an html-all target. With the default at true, every generator-react site would then need @node-core/doc-kit installed, and flipping the default breaks things anyway. @doc-kit/generator-react is 0.5.3, so this is a minor bump.

Why splice it in front of html instead of running it after? html is terminal. It writes to disk and returns nothing, so a generator after it has nothing to read. Building all.html in jsx-ast would build every module's JSX a second time. Spliced in with dependent, html-all keeps only each module page's data and headings, never its content, and html compiles all.html from the programs it already compiled. Only html knows where it wrote each of those, which is why the page importing them is compiled there; its code no longer special-cases all.html.

Why right after the last module page, and not at the end? On Node.js's docs, section-pages passes the entries of the 69 module pages through as they come, and the ~720 section pages after them (795 pages in all). html-all uses jsx-ast's manifest to know when the last module page is through, so all.html gets compiled and put at the front of the render queue (as on main) while the section pages are still being built. For that the manifest has to come early: on #1241, jsx-ast now describes its pages ahead of worker.stream, so the manifest goes out once the input ends rather than with the last 12-18 page chunks. With one thread html-all gets every page at once, so all.html simply goes after them. On Aviv's 2d41aa7, html collects and sorts every page before compiling any, so where all.html arrives wouldn't matter there.

Why does a refactor: PR change how all.html renders? <>{content0()}{content1()}…</> created every part's elements before rendering any of them. render-to-string keeps a fragment's children on their parent, but not a single element, so with each part rendered through a component that returns one element, its tree can be freed once it's rendered. The output stays identical, ids included, because useId counts in the same order. It lives here and not in #1241 because it rewrites the same parts branch this PR makes generic. A composed page also stops scanning its program for component names, which would take the new wrappers for configured components with the same names.

Why does Node.js have to add html-all? The web.doc-kit.config.mjs in nodejs/node#66713 targets legacy-json-all, section-pages and llms-txt and drops legacy-html-all, so all.html can only come from html. Without html-all in target, that PR's test/doctool/test-docs.mjs fails with all.html was not generated, and its Makefile lists out/doc/api/all.html as an output. The fix is one line in target.

Benchmarks

Full Node.js docs on Node 26, medians of 5 interleaved runs, peak = RSS summed over the process tree. Site build = -t orama-db -t json -t llms-txt -t section-pages, plus -t html-all here. #1241 builds the same all.html through generateAllPage.

-p main #1241 this PR
4 9.82s / 1728MB 8.12s / 2179MB 8.09s / 2159MB (-18% / +25%)
6 8.56s / 2280MB 6.82s / 2821MB 7.09s / 2814MB (-17% / +23%)

Against #1241 the peak is the same and time is flat at 4 threads. At 6 it's 0.27s slower in this round; two earlier sets of runs on the same change, before #1241's manifest fix, had it at +2% (about 0.1s) at 6 threads and +0-1% at 4.

  • This PR before and after the fix it's rebased on: 8.17s → 8.09s and 2316 → 2159MB (-7%) at 4 threads, 7.20s → 7.09s and 2730 → 2814MB (+3%) at 6.
  • -p 1 under --max-old-space-size=512: the site build passed all 11 runs at ~1000MB peak (912 to 1063MB), while main hit V8's out-of-memory abort in 1 of 8 runs and the commit before perf: let worker.stream take chunks as they arrive #1237 in 3 of 5. Passing still depends on GC timing, as perf: keep memory use down at low thread counts #1179 found. The legacy build passed 3 of 3 on both, 877MB vs 885MB.

Validation

Added tests for html-all (all.html right after its last part in sidebar order, and after every page given at once as with one thread), and for html assembling a composed page from its parts' content, numbering their ids across parts, and importing no component into it whatever the components are named. With the html-all target added, the output on 12 of Node.js's API docs is byte-identical to main's, all.html included, at 1 and 4 threads.

Related Issues

Refs: #1205

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 Nov 3, 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 Nov 11, 2026 12:07am UTC

Request Review

@ovflowd
ovflowd added this pull request to stack #1244 November 3, 2026 19:34
@codecov

codecov Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.55%. Comparing base (54ee66f) to head (78217c5).

Additional details and impacted files
@@                 Coverage Diff                  @@
##           perf/stream-html    #1242      +/-   ##
====================================================
+ Coverage             94.52%   94.55%   +0.03%     
====================================================
  Files                   295      297       +2     
  Lines                 31057    31220     +163     
  Branches               3173     3187      +14     
====================================================
+ Hits                  29357    29521     +164     
+ Misses                 1678     1677       -1     
  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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Nov 3, 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 ↗ 78217c5 2026-11-11T00:07:44.987Z View logs ↗
  • Build: Success ✅

View logs ↗
No Preview URL. Enable ↗ 71861b9 2026-11-10T20:53:54.997Z View logs ↗
  • Build: Success ✅

View logs ↗
No Preview URL. Enable ↗ 54e59de 2026-11-09T13:26:23.994Z View logs ↗
  • Build: Success ✅

View logs ↗
No Preview URL. Enable ↗ a8bfd7f 2026-11-09T11:04:20.654Z View logs ↗
  • Build: Success ✅

View logs ↗
No Preview URL. Enable ↗ 822cbaf 2026-11-09T10:38:12.588Z View logs ↗
  • Build: Success ✅

View logs ↗
No Preview URL. Enable ↗ 0931ca2 2026-11-09T02:27:17.176Z View logs ↗
  • Build: Success ✅

View logs ↗
No Preview URL. Enable ↗ 78ab04c 2026-11-06T19:45:01.018Z View logs ↗
  • Build: Success ✅

View logs ↗
No Preview URL. Enable ↗ 62872b1 2026-11-06T19:32:25.941Z View logs ↗
  • Build: Success ✅

View logs ↗
No Preview URL. Enable ↗ 6f084a9 2026-11-06T13:38:44.039Z View logs ↗
  • Build: Success ✅

View logs ↗
No Preview URL. Enable ↗ 529d486 2026-11-04T18:25:29.047Z View logs ↗

View all previews: View all previews ↗

all.html, every module page's content on one page, is Node.js's, but
html built it in, behind generateAllPage, and the streaming work spread
its bookkeeping through generate.mjs.

It's now a generator of @node-core/doc-kit, html-all, spliced between
jsx-ast and html the way section-pages is between metadata and jsx-ast.
It passes the pages on, adds all.html's metadata to the manifest, and
right after the last module page adds all.html as a page made of
theirs, in sidebar order. html only knows that an item with parts, the
pages before it that it's made of, is compiled from their content and
rendered first, one part at a time instead of building every part's
elements before rendering any; generateAllPage goes away. Built with
the Node.js preset and the html-all target, the Node.js API docs come
out byte-identical, all.html included, at 1 and 4 threads.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Assisted-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…t lives

The code and types already called it a composed page, while the docs,
comments and tests said a page made of other pages. They all say
composed page now.

html-all no longer adds all.html at the end when no manifest came, as
jsx-ast always sends one, and each page it lists arrives. html's test
of the bundles' timing no longer gives it all.html, whose place
html-all's tests check, the composed page's program test only checks
what it imports, as html's tests render one, and html's tests build
all.html's metadata with createSyntheticHead.

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

This branch was successfully deployed

1 active deployment
Preview – api-docs-tooling — 78217c59 Deployed Nov 11, 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.

2 participants