-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathindex.mjs
190 lines (158 loc) · 6.5 KB
/
index.mjs
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
'use strict';
import { cp, readFile, rm, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { minify } from 'html-minifier-terser';
import buildContent from './utils/buildContent.mjs';
import dropdowns from './utils/buildDropdowns.mjs';
import tableOfContents from './utils/tableOfContents.mjs';
import { groupNodesByModule } from '../../utils/generators.mjs';
import { getRemarkRehype } from '../../utils/remark.mjs';
/**
* @typedef {{
* api: string;
* added: string;
* section: string;
* version: string;
* toc: string;
* nav: string;
* content: string;
* }} TemplateValues
*
* 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
*
* @typedef {Array<ApiDocMetadataEntry>} Input
*
* @type {import('../types.d.ts').GeneratorMetadata<Input, Array<TemplateValues>>}
*/
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: 'ast',
/**
* Generates the legacy version of the API docs in HTML
* @param {Input} input
* @param {Partial<GeneratorOptions>} options
*/
async generate(input, { releases, version, output }) {
// This array holds all the generated values for each module
const generatedValues = [];
// Gets a Remark Processor that parses Markdown to minified HTML
const remarkRehypeProcessor = getRemarkRehype();
const groupedModules = groupNodesByModule(input);
// Current directory path relative to the `index.mjs` file
const baseDir = import.meta.dirname;
// Reads the API template.html file to be used as a base for the HTML files
const apiTemplate = await readFile(join(baseDir, 'template.html'), 'utf-8');
// Gets the first nodes of each module, which is considered the "head" of the module
// and sorts them alphabetically by the "name" property of the heading
const headNodes = input
.filter(node => node.heading.depth === 1)
.sort((a, b) => a.heading.data.name.localeCompare(b.heading.data.name));
// Generates the global Table of Contents (Sidebar Navigation)
const parsedSideNav = remarkRehypeProcessor.processSync(
tableOfContents(headNodes, {
maxDepth: 1,
parser: tableOfContents.parseNavigationNode,
})
);
/**
* Replaces the aggregated data from a node within a template
*
* @param {TemplateValues} values The values to be replaced in the template
*/
const replaceTemplateValues = values => {
const { api, added, section, version, toc, nav, content } = values;
return apiTemplate
.replace('__ID__', api)
.replace(/__FILENAME__/g, api)
.replace(/__SECTION__/g, section)
.replace(/__VERSION__/g, version)
.replace(/__TOC__/g, tableOfContents.wrapToC(toc))
.replace(/__GTOC__/g, nav)
.replace('__CONTENT__', content)
.replace(/__TOC_PICKER__/g, dropdowns.buildToC(toc))
.replace(/__GTOC_PICKER__/g, dropdowns.buildNavigation(nav))
.replace('__ALTDOCS__', dropdowns.buildVersions(api, added, releases))
.replace('__EDIT_ON_GITHUB__', dropdowns.buildGitHub(api));
};
/**
* Processes each module node to generate the HTML content
*
* @param {ApiDocMetadataEntry} head The name of the module to be generated
* @param {string} template The template to be used to generate the HTML content
*/
const processModuleNodes = head => {
const nodes = groupedModules.get(head.api);
// Replaces the entry corresponding to the current module
// as an active entry within the side navigation
const activeSideNav = String(parsedSideNav).replace(
`class="nav-${head.api}`,
`class="nav-${head.api} active`
);
// Generates the Table of Contents for the current module, which is appended
// to the top of the page and also to a dropdown
const parsedToC = remarkRehypeProcessor.processSync(
tableOfContents(nodes, {
maxDepth: 4,
parser: tableOfContents.parseToCNode,
})
);
// Builds the content of the module, including all sections,
// stability indexes, and content for the current file
const parsedContent = buildContent(
headNodes,
nodes,
remarkRehypeProcessor
);
// In case there's no Heading, we make a little capitalization of the filename
const apiAsHeading = head.api.charAt(0).toUpperCase() + head.api.slice(1);
const generatedTemplate = {
api: head.api,
added: head.introduced_in ?? '',
section: head.heading.data.name || apiAsHeading,
version: `v${version.toString()}`,
toc: String(parsedToC),
nav: String(activeSideNav),
content: parsedContent,
};
// Adds the generated template to the list of generated values
generatedValues.push(generatedTemplate);
// Replaces all the values within the template for the current doc
return replaceTemplateValues(generatedTemplate);
};
for (const node of headNodes) {
const result = processModuleNodes(node);
if (output) {
// We minify the html result to reduce the file size and keep it "clean"
const minified = await minify(result, {
collapseWhitespace: true,
minifyJS: true,
minifyCSS: true,
});
await writeFile(join(output, `${node.api}.html`), minified);
}
}
if (output) {
// Define the output folder for API docs assets
const assetsFolder = join(output, 'assets');
// Removes the current assets directory to copy the new assets
// and prevent stale assets from existing in the output directory
// If the path does not exists, it will simply ignore and continue
await rm(assetsFolder, { recursive: true, force: true });
// We copy all the other assets to the output folder at the end of the process
// to ensure that all latest changes on the styles are applied to the output
// Note.: This is not meant to be used for DX/developer purposes.
await cp(join(baseDir, 'assets'), assetsFolder, {
recursive: true,
force: true,
});
}
return generatedValues;
},
};