AstroJS
AstroJS is a framework for static website building that has a lot of exciting features like
- reducing page size (and therefore faster loading speed);
- being framework agnostic (React, Svelte, what have you are all supported);
- lowering client-side scripting (a lot of processing is done on the server and AstroJS ships pure HTML by default);
- being component-based and supporting component de-hydration (components are loaded lazily when needed);
- support for MDX (Markdown + JSX), TypeScript, CSS Pre-Processing, and CSS frameworks (like Tailwind CSS); and
- integrated SEO automations (like automatic sitemap generation, RSS feeds, and pagination)
Another great aspect of AstroJS is that it supports extensions of Markdown processing via Remark and Rehype plugins.
Remark and Rehype
Both Remark and Rehype are frameworks to transform documents by first transforming them to abstract syntax trees (AST) and then applying pluggable functions to the AST before converting it to the target format.
Remark parses Markdown and MDX files and applies transformations. Rehype parses HTML and applies transformations. Both frameworks have a large ecosystem of community plugins available.
đź’ˇ Remark and Rehype may be combined if we export HTML after transformation with Remark, for example by using the
remark-rehype
plugin. AstroJS automatically does this for use, so we do not need to worry about the transformation and may directly write plugins for Remark and Rehype.
Extending AstroJS With Remark and Rehype
A Remark or Rehype plugin is a higher-order function—a function returning another function in this case—that takes an options
argument for configuration and then returns a function that receives the abstract syntax tree (AST) of the parsed Markdown or HTML document.
The abstract syntax tree for both tools looks something like this:
{
type: 'root',
children: [
{ type: ..., children: [Array], position: [Object] },
// ...
],
position: {
start: { line: 1, column: 1, offset: 0 },
end: { line: 44, column: 11, offset: 1201 }
}
}
Plugins for both Remark and Rehype may be registered in the Markdown or MDX integrations in astro.config.mjs
. Below, is an example configuration of the MDX integration with a Remark plugin theRemarkPlugin
:
import { defineConfig } from "astro/config";
import theRemarkPlugin from "src/remark/the-remark-plugin"
// https://astro.build/config
export default defineConfig({
integrations: [
mdx({
remarkPlugins: [theRemarkPlugin]
})
]
});
đź’ˇ If we want to add a Remark or Rehype plugin to the Markdown processor, we have to add it to
markdown.remarkPlugins
respectivelymarkdown.rehypePlugins
of the configuration object passed todefineConfig
instead.
Example: Enabling rehype-mermaid
in AstroJS
Lets, assume we want to add rendered Mermaid diagrams to our page by transforming the source code in fenced code blocks like the following:
```mermaid
flowchart LR
A[Hard] -->|Text| B(Round)
B --> C{Decision}
C -->|One| D[Result 1]
C -->|Two| E[Result 2]
```
Thankfully, a Rehype plugin to generate Mermaid diagrams rehype-mermaid
from HTML <pre>
or <code>
elements with Mermaid diagram source code already exists. Adding it to our Markdown or MDX processing pipeline is therefore as easy as installing it with NPM and adding it to the configuration as shown above.
The plugin's documentation, however, states that it only converts <pre class="mermaid">
 and <code class="language-mermaid">
elements to rendered diagrams in SVG format.
And, unfortunately, if we add a fenced code block with language mermaid
to our post, the element produced by remark is a <pre>
element with a class list like astro-code github-dark
. To get our rendered Mermaid diagram, we have to add the class mermaid
to this element.
With the tooling provided for Rehype via projects in the unist
ecosystem, this is luckily rather straight forward. To help finding the nodes of the AST that we are concerned with and transforming them as need, we may use the visit
function from unist-util-visit
.
This function, may—for simple scenarios like ours—be defined with only two parameters
-
ast
(the AST's root) and - a
visitor
function that applies our changes.
Omitting some implementation details, our Rehype plugin addMermaidClass
to add the mermaid
class to Mermaid code blocks then may be written as follows:
import { visit, CONTINUE } from "unist-util-visit"
import type { Plugin } from 'unified';
import type { Root, Element } from 'hast';
/* ... */
const visitor = (node: any) => {
/* ... */
}
const addMermaidClass: Plugin<void[], Root> = () =>
(ast: Root) => visit(ast, visitor)
export default addMermaidClass
Let's now take a look at the implementation of the visitor
function.
const dataLanguageMermaid = "mermaid"
const typeElement = "element"
const tagNamePre = "pre"
const classMermaid = dataLanguageMermaid
const isPreElement = (node: any) => typeof node.type !== undefined && node.type === typeElement
&& node.tagName !== undefined && node.tagName === tagNamePre
&& node.properties !== undefined && node.properties.dataLanguage === dataLanguageMermaid
const visitor = (node: any) => {
if(!isPreElement(node)) {
return CONTINUE
}
const element = node as Element
const properties = element.properties
const className = properties.className as Array<string>
properties.className = [...className, classMermaid]
return CONTINUE
}
The important things to note are
- the first conditional which assures that we are visiting the AST representation of a
<pre>
element and skips all other elements and - the in-place modification of the
node.properties.className
array.
đź’ˇ Note that the
visit
function should return an object of typeAction
instead of the visited node. In this case, we return theCONTINUE
action that tells Remark to continue traversing the AST.
The last step is to import the plugin and add it to the configuration of the MDX integration before the rehypeMermaid
plugin. My AstroJS at the time of writing looks like this.
import { defineConfig } from "astro/config";
import icon from "astro-icon";
import tailwind from "@astrojs/tailwind";
import mdx from "@astrojs/mdx";
import rehypeMermaid from 'rehype-mermaid'
import addMermaidClass from "src/rehype/add-mermaid-class"
// https://astro.build/config
export default defineConfig({
integrations: [
tailwind(),
icon(),
mdx({
rehypePlugins: [addMermaidClass, rehypeMermaid]
})
]
});
đź’ˇ Adding the
addMermaidClass
plugin before therehypeMermaid
plugin is important since the array order defines the execution order of the plugins and we need to change the class name before passing the AST on to therehypeMermaid
plugin.
The rendered result of the Mermaid diagram is shown below: