An important component of a blog is the ability to display content. In this post, I will describe how I set up and enhanced the markdown renderer's functionality with syntax highlighting and graphs.
Rendering Markdown
Markdown is a simple markup language for creating formatted documents. You can italicize, bold, create headers, embed images, and display code
. Most renderers for the web, by default, convert these directly into their HTML counterparts. As a consequence, there is no syntax highlighting or graph rendering; this makes extensibility all the more important in a renderer.
One of the most popular Markdown renderers is marked
. It has a large ecosystem and is easy to configure. To render the markdown on my server, I pass it into a configured marked
instance.
// Shared instance
import { Marked } from "marked";
const marked = new Marked();
...
// '/blog/:id' endpoint; post-database query
const output = await marked.parse(article.content);
Next, the data is given to the template:
// '/blog/:id' endpoint; post-markdown render
const article = {..., content: output};
return response.viewAsync('blog.eta', {
...,
article: article
});
<!-- blog.eta -->
<div class="article-body-text">
<%~ it.article.content %>
</div>
The markdown now renders to the page. That's all you need for basic server-side markdown rendering. If you insert a code block, though, there will be no syntax highlighting. Let's fix that.
Syntax Highlighting
Here's how one would make a code block with markdown:
```js
console.log("Syntax highlighting");
```
Notice the js
on the first line, just right of the backticks. That identifier tells the renderer what language we are writing in. Using this information, the right combination of plugins can highlight this for us (as evidenced earlier in this article).
To our aid comes marked-highlight
and higlight.js
. The former functions as a MarkedExtension
, so we can pass it directly into the Marked
constructor.
import { Marked } from "marked";
import { markedHighlight } from "marked-highlight";
const marked = new Marked(
markedHighlight(...)
);
marked-highlight
doesn't actually do any of the highlighting on its own. Instead, the extension depends upon a separate syntax highlighter of your choice. highlight.js
seems to be a common one. In the configuration object of the markedHighlight
constructor, we can tell it to use our syntax highlighter:
import { Marked } from "marked";
import { markedHighlight } from "marked-highlight";
import hljs from "highlight.js";
const marked = new Marked(
markedHighlight({
emptyLangClass: 'hljs',
langPrefix: 'hljs language-',
highlight(code, lang, info) {
const language = hljs.getLanguage(lang)
? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
}
})
);
The emptyLangClass
and langPrefix
properties specify the classes used to color the different elements generated by the highlighter. highlight.js
, in particular, has a multitude of themes. We can include their stylesheet in the HTML document at some point above the article:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/tokyo-night-dark.min.css">
And that's it for syntax highlighting. Code should now be vibrantly highlighted according to whichever language you specify.
Graphs
Written language is cool, but some things are easier to get across with a visual. There are many possible approaches to this; I could easily just embed SVGs made ahead of time. The approach I went with turns text into graphs.
Mermaid is a tool that loads up on the front-end and automatically converts a human-readable language into graphs. The syntax generally sees the chart type on one line, some parameters next to it, and connections on the following lines, but this may vary between the many different chart and diagram types included. Here's an example:
```mermaid
graph LR
a[One thing]-->b[Another]
```
Given that the library sits on the front-end, it's easy enough to transform this into a graph. Mermaid is looking for pre
tags with the class mermaid
; we can tell marked
to output different tags for codeblocks with the 'mermaid' language:
marked.use({
renderer: {
code(code : Tokens.Code) {
if (code.lang == "mermaid") {
return `<pre class='mermaid'>${code.text}</pre>`;
} else {
return `<pre><code class='hljs language-${code.lang}'>${code.text}</code></pre>`;
}
}
}
});
Next, we need to include Mermaid on the client:
// A script included by the webpage
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11.6.0/+esm";
mermaid.initialize({ theme: 'dark' });
That's it. One thing leads to another, and you have your chart:
graph LR a[One thing]-->b[Another]
Conclusion
It's not too complicated to set up. Thanks to the extensibility of marked
, these features can be added in under a half hour. Though it wasn't my intention, this article comes off like a tutorial. I'll make the next blog post about something more intriguing.