Skip to content

Commit 113fbbe

Browse files
feat(web): introduce stability overview
1 parent 439cfad commit 113fbbe

6 files changed

Lines changed: 124 additions & 9 deletions

File tree

src/generators/jsx-ast/generate.mjs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ const remarkRecma = getRemarkRecma();
1616
*
1717
* @type {import('./types').Generator['processChunk']}
1818
*/
19-
export async function processChunk(slicedInput, itemIndices, docPages) {
19+
export async function processChunk(
20+
slicedInput,
21+
itemIndices,
22+
{ docPages, stabilityOverviewEntries }
23+
) {
2024
const results = [];
2125

2226
for (const idx of itemIndices) {
@@ -28,7 +32,8 @@ export async function processChunk(slicedInput, itemIndices, docPages) {
2832
entries,
2933
head,
3034
sideBarProps,
31-
remarkRecma
35+
remarkRecma,
36+
stabilityOverviewEntries
3237
);
3338

3439
results.push(content);
@@ -54,14 +59,30 @@ export async function* generate(input, worker) {
5459
? config.index.map(({ section, api }) => [section, `${api}.html`])
5560
: headNodes.map(node => [node.heading.data.name, `${node.api}.html`]);
5661

62+
// Pre-compute stability overview data once — avoid serialising full AST nodes to workers
63+
const stabilityOverviewEntries = headNodes
64+
.filter(node => node.stability?.children?.length)
65+
.map(({ api, heading, stability }) => {
66+
const [{ data }] = stability.children;
67+
return {
68+
api,
69+
name: heading.data.name,
70+
stabilityIndex: parseInt(data.index, 10),
71+
stabilityDescription: data.description.split('. ')[0],
72+
};
73+
});
74+
5775
// Create sliced input: each item contains head + its module's entries
5876
// This avoids sending all 4700+ entries to every worker
5977
const entries = headNodes.map(head => ({
6078
head,
6179
entries: groupedModules.get(head.api),
6280
}));
6381

64-
for await (const chunkResult of worker.stream(entries, entries, docPages)) {
82+
for await (const chunkResult of worker.stream(entries, entries, {
83+
docPages,
84+
stabilityOverviewEntries,
85+
})) {
6586
yield chunkResult;
6687
}
6788
}

src/generators/jsx-ast/utils/buildContent.mjs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ export const transformHeadingNode = async (
253253
* @param {ApiDocMetadataEntry} entry - The API metadata entry to process
254254
* @param {import('unified').Processor} remark - The remark processor
255255
*/
256-
export const processEntry = (entry, remark) => {
256+
export const processEntry = (entry, remark, stabilityOverviewEntries = []) => {
257257
// Deep copy content to avoid mutations on original
258258
const content = structuredClone(entry.content);
259259

@@ -272,6 +272,18 @@ export const processEntry = (entry, remark) => {
272272
(node, idx, parent) => (parent.children[idx] = createPropertyTable(node))
273273
);
274274

275+
// Inject the stability overview table where the slot tag is present
276+
if (
277+
stabilityOverviewEntries.length &&
278+
entry.tags.includes('STABILITY_OVERVIEW_SLOT_BEGIN')
279+
) {
280+
content.children.push(
281+
createJSXElement(JSX_IMPORTS.StabilityOverview.name, {
282+
entries: stabilityOverviewEntries,
283+
})
284+
);
285+
}
286+
275287
return content;
276288
};
277289

@@ -286,7 +298,8 @@ export const createDocumentLayout = (
286298
entries,
287299
sideBarProps,
288300
metaBarProps,
289-
remark
301+
remark,
302+
stabilityOverviewEntries = []
290303
) =>
291304
createTree('root', [
292305
createJSXElement(JSX_IMPORTS.NavBar.name),
@@ -302,7 +315,9 @@ export const createDocumentLayout = (
302315
createElement('br'),
303316
createElement(
304317
'main',
305-
entries.map(entry => processEntry(entry, remark))
318+
entries.map(entry =>
319+
processEntry(entry, remark, stabilityOverviewEntries)
320+
)
306321
),
307322
]),
308323
createJSXElement(JSX_IMPORTS.MetaBar.name, metaBarProps),
@@ -321,7 +336,13 @@ export const createDocumentLayout = (
321336
* @param {import('unified').Processor} remark - Remark processor instance for markdown processing
322337
* @returns {Promise<JSXContent>}
323338
*/
324-
const buildContent = async (metadataEntries, head, sideBarProps, remark) => {
339+
const buildContent = async (
340+
metadataEntries,
341+
head,
342+
sideBarProps,
343+
remark,
344+
stabilityOverviewEntries = []
345+
) => {
325346
// Build props for the MetaBar from head and entries
326347
const metaBarProps = buildMetaBarProps(head, metadataEntries);
327348

@@ -330,7 +351,8 @@ const buildContent = async (metadataEntries, head, sideBarProps, remark) => {
330351
metadataEntries,
331352
sideBarProps,
332353
metaBarProps,
333-
remark
354+
remark,
355+
stabilityOverviewEntries
334356
);
335357

336358
// Run remark processor to transform AST (parse markdown, plugins, etc.)

src/generators/web/constants.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ export const JSX_IMPORTS = {
8686
name: 'ArrowUpRightIcon',
8787
source: '@heroicons/react/24/solid/ArrowUpRightIcon',
8888
},
89+
StabilityOverview: {
90+
name: 'StabilityOverview',
91+
source: resolve(ROOT, './ui/components/StabilityOverview'),
92+
},
8993
};
9094

9195
/**

src/generators/web/generate.mjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
import { readFile, writeFile } from 'node:fs/promises';
3+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
44
import { createRequire } from 'node:module';
55
import { join } from 'node:path';
66

@@ -35,6 +35,8 @@ export async function generate(input) {
3535

3636
// Process all entries together (required for code-split bundles)
3737
if (config.output) {
38+
await mkdir(config.output, { recursive: true });
39+
3840
// Write HTML files
3941
for (const { html, api } of results) {
4042
await writeFile(join(config.output, `${api}.html`), html, 'utf-8');
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import Badge from '@node-core/ui-components/Common/Badge';
2+
3+
import styles from './index.module.css';
4+
5+
const STABILITY_KINDS = ['error', 'warning', 'success', 'info'];
6+
const STABILITY_TOOLTIPS = ['Deprecated', 'Experimental', 'Stable', 'Legacy'];
7+
8+
/**
9+
* @typedef StabilityOverviewEntry
10+
* @property {string} api - The API identifier (basename, e.g. "fs")
11+
* @property {string} name - The human-readable display name of the API module
12+
* @property {number} stabilityIndex - The stability level index (0–3)
13+
* @property {string} stabilityDescription - First sentence of the stability description
14+
*/
15+
16+
/**
17+
* Renders a table summarising the stability level of each API module.
18+
*
19+
* @param {{ entries: Array<StabilityOverviewEntry> }} props
20+
*/
21+
export default ({ entries = [] }) => {
22+
if (!entries.length) {
23+
return null;
24+
}
25+
26+
return (
27+
<table className={styles.table}>
28+
<thead>
29+
<tr>
30+
<th>API</th>
31+
<th>Stability</th>
32+
</tr>
33+
</thead>
34+
<tbody>
35+
{entries.map(({ api, name, stabilityIndex, stabilityDescription }) => (
36+
<tr key={api}>
37+
<td>
38+
<a href={`${api}.html`}>{name}</a>
39+
</td>
40+
<td className={styles.stabilityCell}>
41+
<Badge
42+
kind={STABILITY_KINDS[stabilityIndex]}
43+
data-tooltip={STABILITY_TOOLTIPS[stabilityIndex]}
44+
aria-label={`Stability: ${STABILITY_TOOLTIPS[stabilityIndex]}`}
45+
>
46+
{stabilityIndex}
47+
</Badge>
48+
49+
{` ${stabilityDescription}`}
50+
</td>
51+
</tr>
52+
))}
53+
</tbody>
54+
</table>
55+
);
56+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.table {
2+
width: 100%;
3+
}
4+
5+
.stabilityCell {
6+
display: flex;
7+
align-items: center;
8+
gap: 0.4rem;
9+
flex-wrap: wrap;
10+
}

0 commit comments

Comments
 (0)