Skip to content

Commit bd9645e

Browse files
devversionjelbourn
authored andcommitted
build: markdown-to-html rule should apply docs-specific transforms (#14315)
Currently when building the overview and guide files with Bazel, the following things are missing: * Replacing `<!-- example` comments with the corresponding HTML elements * Wrapping the HTML output with a `<div class="docs-markdown">` element * Rewriting relative links to the proper public guide URL. * Convert headings to "anchored" headings that can be referenced through URL fragments. All of these points are handled with this commit.
1 parent 47e7aac commit bd9645e

File tree

6 files changed

+110
-19
lines changed

6 files changed

+110
-19
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"@types/hammerjs": "^2.0.35",
7070
"@types/inquirer": "^0.0.43",
7171
"@types/jasmine": "^3.0.0",
72+
"@types/marked": "^0.4.2",
7273
"@types/merge2": "^0.3.30",
7374
"@types/minimist": "^1.2.0",
7475
"@types/mock-fs": "^3.6.30",

tools/markdown-to-html/BUILD.bazel

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ load("//tools:defaults.bzl", "ts_library")
55

66
ts_library(
77
name = "transform-markdown",
8-
srcs = [":transform-markdown.ts"],
9-
deps = ["@matdeps//@types/node"],
8+
srcs = glob(["**/*.ts"]),
9+
deps = [
10+
"@matdeps//@types/node",
11+
"@matdeps//@types/marked"
12+
],
1013
tsconfig = ":tsconfig.json",
1114
)
1215

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {MarkedOptions, Renderer} from 'marked';
2+
import {basename, extname} from 'path';
3+
import {highlightCodeBlock} from './highlight-code-block';
4+
5+
/** Regular expression that matches whitespace. */
6+
const whitespaceRegex = /\W+/g;
7+
8+
/** Regular expression that matches example comments. */
9+
const exampleCommentRegex = /<!--\W*example\(([^)]+)\)\W*-->/g;
10+
11+
/**
12+
* Custom renderer for marked that will be used to transform markdown files to HTML
13+
* files that can be used in the Angular Material docs.
14+
*/
15+
export class DocsMarkdownRenderer extends Renderer {
16+
17+
constructor(options?: MarkedOptions) {
18+
super({highlight: highlightCodeBlock, baseUrl: 'material.angular.io/', ...options});
19+
}
20+
21+
/**
22+
* Transforms a markdown heading into the corresponding HTML output. In our case, we
23+
* want to create a header-link for each H3 and H4 heading. This allows users to jump to
24+
* specific parts of the docs.
25+
*/
26+
heading(label: string, level: number, _raw: string) {
27+
if (level === 3 || level === 4) {
28+
const headingId = label.toLowerCase().replace(whitespaceRegex, '-');
29+
30+
return `
31+
<h${level} id="${headingId}" class="docs-header-link">
32+
<span header-link="${headingId}"></span>
33+
${label}
34+
</h${level}>
35+
`;
36+
}
37+
38+
return `<h${level}>${label}</h${level}>`;
39+
}
40+
41+
/** Transforms markdown links into the corresponding HTML output. */
42+
link(href: string, title: string, text: string) {
43+
// We only want to fix up markdown links that are relative and do not refer to guides already.
44+
// Otherwise we always map the link to the "guide/" path.
45+
// TODO(devversion): remove this logic and just disallow relative paths.
46+
if (!href.startsWith('http') && !href.includes('guide/')) {
47+
return super.link(`guide/${basename(href, extname(href))}`, title, text);
48+
}
49+
50+
return super.link(href, title, text);
51+
}
52+
53+
/**
54+
* Method that will be called whenever inline HTML is processed by marked. In that case,
55+
* we can easily transform the example comments into real HTML elements. For example:
56+
*
57+
* `<!-- example(name) -->` turns into `<div material-docs-example="name"></div>`
58+
*/
59+
html(html: string) {
60+
html = html.replace(exampleCommentRegex, (_match: string, name: string) =>
61+
`<div material-docs-example="${name}"></div>`
62+
);
63+
64+
return super.html(html);
65+
}
66+
67+
/**
68+
* Method that will be called after a markdown file has been transformed to HTML. This method
69+
* can be used to finalize the content (e.g. by adding an additional wrapper HTML element)
70+
*/
71+
finalizeOutput(output: string): string {
72+
return `<div class="docs-markdown">${output}</div>`;
73+
}
74+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// These type lacks type definitions.
2+
const highlightJs = require('highlight.js');
3+
4+
/**
5+
* Transforms a given code block into its corresponding HTML output. We do this using
6+
* highlight.js because it allows us to show colored code blocks in our documentation.
7+
*/
8+
export function highlightCodeBlock(code: string, language: string) {
9+
if (language) {
10+
return highlightJs.highlight(
11+
language.toLowerCase() === 'ts' ? 'typescript' : language, code).value;
12+
}
13+
14+
return code;
15+
}

tools/markdown-to-html/transform-markdown.ts

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,17 @@
55

66
import {readFileSync, writeFileSync} from 'fs';
77
import {join} from 'path';
8-
9-
// These types lack type definitions.
10-
const marked = require('marked');
11-
const highlightJs = require('highlight.js');
8+
import {DocsMarkdownRenderer} from './docs-marked-renderer';
9+
import * as marked from 'marked';
1210

1311
// Regular expression that matches the markdown extension of a given path.
1412
const markdownExtension = /.md$/;
1513

16-
// Setup the default options for converting markdown to HTML.
17-
marked.setOptions({
18-
// Implement a highlight function that converts the code block into a highlighted
19-
// HTML snippet that uses HighlightJS.
20-
highlight: (code: string, language: string): string => {
21-
if (language) {
22-
return highlightJs.highlight(
23-
language.toLowerCase() === 'ts' ? 'typescript' : language, code).value;
24-
}
25-
return code;
26-
}
27-
});
14+
// Custom markdown renderer for transforming markdown files for the docs.
15+
const markdownRenderer = new DocsMarkdownRenderer();
16+
17+
// Setup our custom docs renderer by default.
18+
marked.setOptions({renderer: markdownRenderer});
2819

2920
if (require.main === module) {
3021
// The script expects the bazel-bin path as first argument. All remaining arguments will be
@@ -35,6 +26,8 @@ if (require.main === module) {
3526
// Bazel bin directory.
3627
inputFiles.forEach(inputPath => {
3728
const outputPath = join(bazelBinPath, inputPath.replace(markdownExtension, '.html'));
38-
writeFileSync(outputPath, marked(readFileSync(inputPath, 'utf8')));
29+
const htmlOutput = markdownRenderer.finalizeOutput(marked(readFileSync(inputPath, 'utf8')));
30+
31+
writeFileSync(outputPath, htmlOutput);
3932
});
4033
}

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,11 @@
772772
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.0.tgz#719551d2352d301ac8b81db732acb6bdc28dbdef"
773773
integrity sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==
774774

775+
"@types/marked@^0.4.2":
776+
version "0.4.2"
777+
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.4.2.tgz#64a89e53ea37f61cc0f3ee1732c555c2dbf6452f"
778+
integrity sha512-cDB930/7MbzaGF6U3IwSQp6XBru8xWajF5PV2YZZeV8DyiliTuld11afVztGI9+yJZ29il5E+NpGA6ooV/Cjkg==
779+
775780
"@types/merge2@^0.3.30":
776781
version "0.3.30"
777782
resolved "https://registry.yarnpkg.com/@types/merge2/-/merge2-0.3.30.tgz#9e39d04f6fe4f36fa7477566cad1faf80b2a671f"

0 commit comments

Comments
 (0)