Skip to content

Commit

Permalink
chore: more code refactors and cleanups
Browse files Browse the repository at this point in the history
  • Loading branch information
ovflowd committed Aug 10, 2024
1 parent 147a349 commit 71c39f7
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 110 deletions.
24 changes: 21 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
"test": "node --test",
"test:watch": "node --test --watch",
"test:coverage": "node --experimental-test-coverage --test",
"prepare": "husky"
"prepare": "husky",
"run": "node bin/cli.mjs",
"watch": "node --watch bin/cli.mjs"
},
"bin": {
"api-docs-tooling": "./bin/cli.mjs"
Expand All @@ -28,6 +30,7 @@
"commander": "^12.1.0",
"github-slugger": "^2.0.0",
"glob": "^11.0.0",
"hastscript": "^9.0.0",
"html-minifier-terser": "^7.2.0",
"rehype-stringify": "^10.0.0",
"remark": "^15.0.1",
Expand Down
12 changes: 6 additions & 6 deletions shiki.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import { u as createTree } from 'unist-builder';
import { h as createElement } from 'hastscript';

import { getWasmInstance } from '@shikijs/core/wasm-inlined';

Expand All @@ -21,11 +21,11 @@ import shikiNordTheme from 'shiki/themes/nord.mjs';

// Creates a static button element which is used for the "copy" button
// within codeboxes for copying the code to the clipboard
const copyButtonElement = createTree('element', {
tagName: 'button',
properties: { class: 'copy-button' },
children: [createTree('text', 'copy')],
});
const copyButtonElement = createElement(
'button',
{ class: 'copy-button' },
createElement('text', 'copy')
);

/**
* @TODO: Use `shiki.config.mjs` from nodejs/nodejs.org
Expand Down
2 changes: 1 addition & 1 deletion src/generators/legacy-html/assets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ tt,
}

.api_stability code {
background-color: rgb(0 0 0 / 10%);
background-color: rgb(0 0 0 / 10%) !important;
}

a code {
Expand Down
162 changes: 81 additions & 81 deletions src/generators/legacy-html/utils/buildContent.mjs
Original file line number Diff line number Diff line change
@@ -1,109 +1,109 @@
'use strict';

import { h as createElement } from 'hastscript';
import { u as createTree } from 'unist-builder';

import {
DOC_API_STABILITY_SECTION_REF_URL,
DOC_NODE_BLOB_BASE_URL,
} from '../../../constants.mjs';

// The regular expression to match the Stability Index prefix
const STABILITY_INDEX_PREFIX_REGEX = /Stability: ([0-5])/;
import { DOC_NODE_BLOB_BASE_URL } from '../../../constants.mjs';

/**
* Builds a Markdown heading for a given node
*
* @param {ApiDocMetadataEntry} node The node to build the Markdown heading for
* @returns {import('unist').Parent} The HTML AST tree of the heading content
*/
const buildNodeHeading = node => {
const buildHeadingElement = node => {
const [, headingId] = node.slug.split('#');

// Creates the anchor element for the heading (floats on the right side)
const anchor = `<span><a class="mark" href="#${headingId}" id="${headingId}">#</a></span>`;
// Creates the element that references the link to the heading
const headingLinkElement = createElement(
'span',
createElement('a.mark#headingId', { href: `#${headingId}` }, '#')
);

// The extra `\n` (new lines) are done to ensure that remark parses Markdown within HTML
// as it would normally do, as it would not parse it if it were in the same line
return `<h${node.heading.depth + 1}>\n\n${node.heading.text} ${anchor}\n\n</h${node.heading.depth + 1}>`;
// Creates the heading element with the heading text and the link to the heading
return createElement(`h${node.heading.depth + 1}`, [
node.heading.text,
headingLinkElement,
]);
};

/**
* Builds a Markdown Stability Index
*
* @param {ApiDocMetadataEntry} node The node to build the Markdown Stability Index for
* @param {import('remark').remark} remark The Remark instance to be used to process
* @returns {import('unist').Parent} The AST tree of the Stability Index content
*/
const buildStabilityIndex = (node, remark) => {
// Iterates through the Stability Indexes to build the content
return node.stability.children.reduce((acc, stabilityNode) => {
// Transforms the Stability Index AST nodes into a HTML string
let stabilityIndex = remark.stringify(remark.runSync(stabilityNode));

// If the current node being processed is not the documentation node, we
// define a link to the Stability Index section in the documentation
// as they are originally referenced within the `documentation` node.
if (node.api !== 'documentation') {
stabilityIndex = stabilityIndex.replace(
STABILITY_INDEX_PREFIX_REGEX,
match => `<a href="${DOC_API_STABILITY_SECTION_REF_URL}">${match}</a>`
);
}

// Wraps the inner content with an HTML div to apply the CSS styles
acc += `<div class="api_stability api_stability_${stabilityNode.data.index}">${stabilityIndex}</div>`;

return acc;
}, '');
const buildStabilityIndexes = (node, remark) => {
// Iterates over each stability index to create a `div` element with the stability index class
const parsedStabilityIndexes = node.stability.children.map(stabilityNode =>
createElement(
// Creates the `div` element with the class `api_stability` and the stability index class
`div.api_stability.api_stability_${stabilityNode.data.index}`,
// Processed the Markdown nodes into HTML nodes
remark.runSync(stabilityNode).children
)
);

// Creates a tree to surround the Stability Indexes
return createTree('root', parsedStabilityIndexes);
};

/**
* Builds the Metadata Properties into content
*
* @param {ApiDocMetadataEntry} node The node to build to build the properties from
* @returns {import('unist').Parent} The HTML AST tree of the properties content
*/
const buildProperties = node => {
let metadataSection = '<div class="api_metadata">';
const buildMetadataElement = node => {
const metadataElement = createElement('div.api_metadata');

// We use a `span` element to display the source link as a clickable link to the source within Node.js
if (node.sourceLink && node.sourceLink.length) {
metadataSection +=
`<span><b>Source Code:</b>&nbsp;` +
`<a href="${DOC_NODE_BLOB_BASE_URL}${node.sourceLink}">${node.sourceLink}` +
`</a></span>`;
// Creates the source link URL with the base URL and the source link
const sourceLink = `${DOC_NODE_BLOB_BASE_URL}${node.sourceLink}`;

// Creates the source link element with the source link and the source link text
const sourceLinkElement = createElement('span', [
createElement('b', 'Source Code: '),
createElement('a', { href: sourceLink }, node.sourceLink),
]);

// Appends the source link element to the metadata element
metadataElement.children.push(sourceLinkElement);
}

// If there are changes, we create a `details` element with a `table` element to display the changes
// Differently from the old API docs, on this version we always enforce a table to display the changes
if (node.changes && node.changes.length) {
const mapChangesIntoTable = node.changes.map(
({ version, description }) =>
// We use double `\n` to ensure that the Markdown is correctly parsed
`<tr><td>${version.join(', ')}</td><td>\n\n${description}\n\n</td></tr>`
// Maps the changes into a `tr` element with the version and the description
const mappedHistoryEntries = node.changes.map(({ version, description }) =>
createElement('tr', [
createElement('td', version.join(', ')),
createElement('td', description),
])
);

metadataSection +=
`<details class="changelog"><summary>History</summary><table>` +
`<thead><tr><th>Version</th><th>Changes</th></tr></thead>` +
`<tbody>${mapChangesIntoTable.reverse().join('\n')}</tbody></table></details>`;
// Creates the history details element with a summary and a table with the changes
const historyDetailsElement = createElement('details.changelog', [
createElement('summary', 'History'),
createElement('table', [
createElement('thead', [
createElement('tr', [
createElement('th', 'Version'),
createElement('th', 'Changes'),
]),
]),
createElement('tbody', mappedHistoryEntries),
]),
]);

// Appends the history details element to the metadata element
metadataElement.children.push(historyDetailsElement);
}

metadataSection += '</div>';

return metadataSection;
};

/**
* Builds the aggregated Markdown metadata content for a given node
*
* @param {ApiDocMetadataEntry} node Builds the content of a given node
* @param {import('remark').remark} remark The Remark instance to be used to process
*/
const buildMetadata = (node, remark) => {
const heading = buildNodeHeading(node);
const stabilityIndeex = buildStabilityIndex(node, remark);
const properties = buildProperties(node);

// Concatenates all the strings and parses with remark into an AST tree
return remark.parse(`${heading}${stabilityIndeex}${properties}`);
// Parses and processes the mixed Markdonw/HTML content into an HTML AST tree
return metadataElement;
};

/**
Expand All @@ -116,23 +116,23 @@ export default (nodes, remark) => {
'root',
// Parses the metadata pieces of each node and the content
nodes.map(node => {
// Parses the metadata into AST, since it they are strings
const parsedMetadata = buildMetadata(node, remark);

// aggregate the two AST trees to then be parsed by runSync
return createTree('root', [parsedMetadata, node.content]);
const headingElement = buildHeadingElement(node);
const metadataElement = buildMetadataElement(node);
const stabilityIndexes = buildStabilityIndexes(node, remark);

// Processes the Markdown AST tree into an HTML AST tree
const processedContent = remark.runSync(node.content);

// Concatenates all the strings and parses with remark into an AST tree
return createElement('section', [
headingElement,
metadataElement,
...stabilityIndexes.children,
...processedContent.children,
]);
})
);

// Processes the nodes to ensure that the Markdown is correctly parsed
const processedNodes = remark.runSync(parsedNodes);

// We transform the outer `div` elements into `section` elements
// This is done to ensure that each section is an actual section in the HTML
processedNodes.children.forEach(node => {
node.tagName = node.tagName === 'div' ? 'section' : node.tagName;
});

// Stringifies the processed nodes to return the final Markdown content
return remark.stringify(processedNodes);
return remark.stringify(parsedNodes);
};
4 changes: 2 additions & 2 deletions src/loader.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ const createLoader = () => {
);

return resolvedFiles.map(async filePath => {
const fileBuffer = await readFile(filePath);
const fileContents = await readFile(filePath, 'utf-8');

return new VFile({ path: filePath, value: fileBuffer });
return new VFile({ path: filePath, value: fileContents });
});
};

Expand Down
19 changes: 8 additions & 11 deletions src/parser.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { SKIP, visit } from 'unist-util-visit';
import createMetadata from './metadata.mjs';
import createQueries from './queries.mjs';

import { transformTypeToReferenceLink } from './utils/parser.mjs';
import { getRemark } from './utils/remark.mjs';
import { createNodeSlugger } from './utils/slugger.mjs';

Expand All @@ -27,6 +26,8 @@ const createParser = () => {
addYAMLMetadata,
addHeadingMetadata,
addStabilityIndexMetadata,
updateTypesToMarkdownLinks,
updateStailityPrefixToMarkdownLinks,
} = createQueries();

/**
Expand All @@ -47,22 +48,18 @@ const createParser = () => {
*/
const metadataCollection = [];

// Creates a new Slugger instance for the current API doc file
const nodeSlugger = createNodeSlugger();

// We allow the API doc VFile to be a Promise of a VFile also,
// hence we want to ensure that it first resolves before we pass it to the parser
const resolvedApiDoc = await Promise.resolve(apiDoc);

// Normalizes all the types in the API doc file to be reference links
// which needs to be done before the actual processing is done
// since we're replacing raw text within the Markdown
// @TODO: This could be moved to another place responsible for handling
// text substitutions at the beginning of the parsing process (as dependencies)
resolvedApiDoc.value = String(resolvedApiDoc.value).replaceAll(
createQueries.QUERIES.normalizeTypes,
transformTypeToReferenceLink
);
updateTypesToMarkdownLinks(resolvedApiDoc);

// Creates a new Slugger instance for the current API doc file
const nodeSlugger = createNodeSlugger();
// Normalizes all the Stability Index prefixes with Markdown links
updateStailityPrefixToMarkdownLinks(resolvedApiDoc);

// Parses the API doc into an AST tree using `unified` and `remark`
const apiDocTree = remarkProcessor.parse(resolvedApiDoc);
Expand Down
Loading

0 comments on commit 71c39f7

Please sign in to comment.