Skip to content

Commit ee581f1

Browse files
authored
feat(web): add all, index, and 404 generation (#780)
* feat(web): add all.html generation
1 parent 6bc834d commit ee581f1

21 files changed

Lines changed: 644 additions & 57 deletions

File tree

package-lock.json

Lines changed: 2 additions & 36 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/generators/jsx-ast/README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ The `jsx-ast` generator converts MDAST (Markdown Abstract Syntax Tree) to JSX AS
66

77
The `jsx-ast` generator accepts the following configuration options:
88

9-
| Name | Type | Default | Description |
10-
| ------- | -------- | -------- | ------------------------------------------------------------------------ |
11-
| `ref` | `string` | `'main'` | Git reference/branch for linking to source files |
12-
| `index` | `array` | - | Array of `{ section, api }` objects defining the documentation structure |
9+
| Name | Type | Default | Description |
10+
| ---------------------- | --------- | -------- | ------------------------------------------------------------------------ |
11+
| `ref` | `string` | `'main'` | Git reference/branch for linking to source files |
12+
| `index` | `array` | - | Array of `{ section, api }` objects defining the documentation structure |
13+
| `generateAllPage` | `boolean` | `true` | When `true`, creates a synthetic JSX AST entry for `all.html` |
14+
| `generateIndexPage` | `boolean` | `true` | When `true`, creates a synthetic JSX AST entry for `index.html` |
15+
| `generateNotFoundPage` | `boolean` | `true` | When `true`, creates a synthetic JSX AST entry for `404.html` |
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import assert from 'node:assert/strict';
2+
import { describe, it } from 'node:test';
3+
4+
import getConfig, { setConfig } from '../../../utils/configuration/index.mjs';
5+
import { generate, processChunk } from '../generate.mjs';
6+
7+
const createEntry = (api, name, { stabilityIndex = '2' } = {}) => {
8+
const heading = {
9+
type: 'heading',
10+
depth: 1,
11+
children: [{ type: 'text', value: name }],
12+
data: { name, text: name, slug: api },
13+
};
14+
15+
return {
16+
api,
17+
path: `/${api}`,
18+
basename: api,
19+
heading,
20+
stability:
21+
stabilityIndex == null
22+
? null
23+
: {
24+
data: {
25+
index: stabilityIndex,
26+
description: `${name} stable. Longer description.`,
27+
},
28+
},
29+
content: {
30+
type: 'root',
31+
children: [
32+
heading,
33+
{
34+
type: 'paragraph',
35+
children: [{ type: 'text', value: `${name} body` }],
36+
},
37+
],
38+
},
39+
};
40+
};
41+
42+
const collect = async generator => {
43+
const results = [];
44+
45+
for await (const chunk of generator) {
46+
results.push(...chunk);
47+
}
48+
49+
return results;
50+
};
51+
52+
const createWorker = seenItems => ({
53+
async *stream(items) {
54+
seenItems.push(...items);
55+
yield items.map(({ head }) => ({ type: 'JSXElement', data: head }));
56+
},
57+
});
58+
59+
describe('jsx-ast generate', () => {
60+
it('does not attach raw section entries to regular JSX content', async () => {
61+
await setConfig({});
62+
63+
const fs = createEntry('fs', 'File system');
64+
const [content] = await processChunk([{ head: fs, entries: [fs] }], [0]);
65+
66+
assert.equal(content.data.api, 'fs');
67+
assert.equal('sectionEntries' in content, false);
68+
});
69+
70+
it('respects jsx-ast synthetic page flags', async () => {
71+
await setConfig({});
72+
73+
const jsxAstConfig = getConfig('jsx-ast');
74+
jsxAstConfig.generateAllPage = false;
75+
jsxAstConfig.generateIndexPage = false;
76+
jsxAstConfig.generateNotFoundPage = false;
77+
78+
const seenItems = [];
79+
const results = await collect(
80+
generate(
81+
[createEntry('index', 'Index'), createEntry('fs', 'File system')],
82+
createWorker(seenItems)
83+
)
84+
);
85+
86+
assert.deepEqual(
87+
seenItems.map(({ head }) => head.api),
88+
['index', 'fs']
89+
);
90+
assert.deepEqual(
91+
results.map(({ data }) => data.api),
92+
['index', 'fs']
93+
);
94+
});
95+
});

src/generators/jsx-ast/generate.mjs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,30 @@
11
import buildContent from './utils/buildContent.mjs';
22
import { getSortedHeadNodes } from './utils/getSortedHeadNodes.mjs';
3+
import { buildNotFoundPage } from './utils/synthetic/404.mjs';
4+
import { buildAllPage } from './utils/synthetic/all.mjs';
5+
import { buildIndexPage } from './utils/synthetic/index.mjs';
6+
import getConfig from '../../utils/configuration/index.mjs';
37
import { groupNodesByModule } from '../../utils/generators.mjs';
48

9+
/**
10+
* Builds JSX content for all configured synthetic pages.
11+
*
12+
* @param {Array<import('../metadata/types').MetadataEntry>} input
13+
*/
14+
const buildSyntheticEntries = async input => {
15+
const config = getConfig('jsx-ast');
16+
17+
const descriptors = [
18+
config.generateAllPage && buildAllPage(input),
19+
config.generateIndexPage && buildIndexPage(input),
20+
config.generateNotFoundPage && buildNotFoundPage(),
21+
].filter(Boolean);
22+
23+
return Promise.all(
24+
descriptors.map(({ head, entries }) => buildContent(entries, head))
25+
);
26+
};
27+
528
/**
629
* Process a chunk of items in a worker thread.
730
* Transforms metadata entries into JSX AST nodes.
@@ -31,18 +54,24 @@ export async function processChunk(slicedInput, itemIndices) {
3154
* @type {import('./types').Generator['generate']}
3255
*/
3356
export async function* generate(input, worker) {
34-
const groupedModules = groupNodesByModule(input);
35-
36-
const headNodes = getSortedHeadNodes(input);
57+
// The synthetic `index` page replaces the Core `index` document.
58+
const moduleInput = input.filter(entry => entry.api !== 'index');
3759

3860
// Create sliced input: each item contains head + its module's entries
3961
// This avoids sending all 4700+ entries to every worker
40-
const entries = headNodes.map(head => ({
62+
const groupedModules = groupNodesByModule(input);
63+
const entries = getSortedHeadNodes(input).map(head => ({
4164
head,
4265
entries: groupedModules.get(head.api),
4366
}));
4467

4568
for await (const chunkResult of worker.stream(entries)) {
4669
yield chunkResult;
4770
}
71+
72+
const syntheticEntries = await buildSyntheticEntries(moduleInput);
73+
74+
if (syntheticEntries.length > 0) {
75+
yield syntheticEntries;
76+
}
4877
}

src/generators/jsx-ast/index.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ export default createLazyGenerator({
1818

1919
defaultConfiguration: {
2020
ref: 'main',
21+
generateAllPage: true,
22+
generateIndexPage: true,
23+
generateNotFoundPage: true,
2124
},
2225

2326
hasParallelProcessor: true,

src/generators/jsx-ast/types.d.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import type { JSXContent } from './utils/buildContent.mjs';
33

44
export type Generator = GeneratorMetadata<
55
{
6-
pageURL: string;
7-
editURL: string;
6+
ref: string;
7+
generateAllPage: boolean;
8+
generateIndexPage: boolean;
9+
generateNotFoundPage: boolean;
810
},
911
Generate<Array<MetadataEntry>, AsyncGenerator<JSXContent>>,
1012
ProcessChunk<
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict';
2+
3+
import { createSyntheticHead, wrapAsEntry } from './synthetic.mjs';
4+
5+
/**
6+
* Builds the page descriptor for `404.html`
7+
*/
8+
export const buildNotFoundPage = () => {
9+
const head = createSyntheticHead('404', 'Page Not Found');
10+
11+
return {
12+
head,
13+
entries: [
14+
wrapAsEntry(head, [
15+
{
16+
type: 'paragraph',
17+
children: [
18+
{
19+
type: 'text',
20+
value:
21+
'The page you requested could not be found. Use the navigation to find the documentation you are looking for, or return to the ',
22+
},
23+
{
24+
type: 'link',
25+
url: 'index.html',
26+
children: [{ type: 'text', value: 'API index' }],
27+
},
28+
{
29+
type: 'text',
30+
value: '.',
31+
},
32+
],
33+
},
34+
]),
35+
],
36+
};
37+
};

0 commit comments

Comments
 (0)