-
Notifications
You must be signed in to change notification settings - Fork 45
Expand file tree
/
Copy pathindex.mjs
More file actions
171 lines (139 loc) · 5.04 KB
/
index.mjs
File metadata and controls
171 lines (139 loc) · 5.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
'use strict';
import { readFile, writeFile, mkdir } from 'node:fs/promises';
import { basename, join } from 'node:path';
import buildContent from './utils/buildContent.mjs';
import { replaceTemplateValues } from './utils/replaceTemplateValues.mjs';
import { safeCopy } from './utils/safeCopy.mjs';
import tableOfContents from './utils/tableOfContents.mjs';
import getConfig from '../../utils/configuration/index.mjs';
import { groupNodesByModule } from '../../utils/generators.mjs';
import { minifyHTML } from '../../utils/html-minifier.mjs';
import { getRemarkRehypeWithShiki } from '../../utils/remark.mjs';
/**
* Creates a heading object with the given name.
* @param {string} name - The name of the heading
* @returns {HeadingMetadataEntry} The heading object
*/
const getHeading = name => ({ data: { depth: 1, name } });
const remarkRehypeProcessor = getRemarkRehypeWithShiki();
/**
*
* This generator generates the legacy HTML pages of the legacy API docs
* for retro-compatibility and while we are implementing the new 'react' and 'html' generators.
*
* This generator is a top-level generator, and it takes the raw AST tree of the API doc files
* and generates the HTML files to the specified output directory from the configuration settings
*
* @type {import('./types').Generator}
*/
export default {
name: 'legacy-html',
version: '1.0.0',
description:
'Generates the legacy version of the API docs in HTML, with the assets and styles included as files',
dependsOn: 'metadata',
defaultConfiguration: {
templatePath: join(import.meta.dirname, 'template.html'),
additionalPathsToCopy: [join(import.meta.dirname, 'assets')],
ref: 'main',
},
/**
* Process a chunk of items in a worker thread.
* Builds HTML template objects - FS operations happen in generate().
*
* Each item is pre-grouped {head, nodes, headNodes} - no need to
* recompute groupNodesByModule for every chunk.
*/
async processChunk(slicedInput, itemIndices, navigation) {
const results = [];
for (const idx of itemIndices) {
const { head, nodes, headNodes } = slicedInput[idx];
const nav = navigation.replace(
`class="nav-${head.api}`,
`class="nav-${head.api} active`
);
const toc = String(
remarkRehypeProcessor.processSync(
tableOfContents(nodes, {
maxDepth: 4,
parser: tableOfContents.parseToCNode,
})
)
);
const content = buildContent(headNodes, nodes, remarkRehypeProcessor);
const apiAsHeading = head.api.charAt(0).toUpperCase() + head.api.slice(1);
const template = {
api: head.api,
added: head.introduced_in ?? '',
section: head.heading.data.name || apiAsHeading,
toc,
nav,
content,
};
results.push(template);
}
return results;
},
/**
* Generates the legacy version of the API docs in HTML
*/
async *generate(input, worker) {
const config = getConfig('legacy-html');
const apiTemplate = await readFile(config.templatePath, 'utf-8');
const groupedModules = groupNodesByModule(input);
const headNodes = input
.filter(node => node.heading.depth === 1)
.toSorted((a, b) =>
a.heading.data.name.localeCompare(b.heading.data.name)
);
const indexOfFiles = config.index
? config.index.map(({ api, section }) => ({
api,
heading: getHeading(section),
}))
: headNodes;
const navigation = String(
remarkRehypeProcessor.processSync(
tableOfContents(indexOfFiles, {
maxDepth: 1,
parser: tableOfContents.parseNavigationNode,
})
)
);
if (config.output) {
for (const path of config.additionalPathsToCopy) {
// Define the output folder for API docs assets
const assetsFolder = join(config.output, basename(path));
// Creates the assets folder if it does not exist
await mkdir(assetsFolder, { recursive: true });
// Copy all files from assets folder to output, skipping unchanged files
await safeCopy(path, assetsFolder);
}
}
// Create sliced input: each item contains head + its module's entries + headNodes reference
// This avoids sending all ~4900 entries to every worker and recomputing groupings
const entries = headNodes.map(head => ({
head,
nodes: groupedModules.get(head.api),
headNodes,
}));
// Stream chunks as they complete - HTML files are written immediately
for await (const chunkResult of worker.stream(
entries,
entries,
navigation
)) {
// Write files for this chunk in the generate method (main thread)
if (config.output) {
for (const template of chunkResult) {
let result = replaceTemplateValues(apiTemplate, template, config);
if (config.minify) {
result = Buffer.from(await minifyHTML(result));
}
await writeFile(join(config.output, `${template.api}.html`), result);
}
}
yield chunkResult;
}
},
};