Creating a Remark Transformer Plugin
gatsby-transformer-remark empowers developers to translate Markdown into HTML to be consumed via Gatsby’s GraphQL API. Blogs and other content based sites can highly benefit from functionality enabled with this plugin. With this plugin, authors of content for the site don’t need to worry about how the site is written or structured but can rather focus on writing engaging posts and content!
In certain instances, a developer may want to customize the content of the Markdown file and extend it functionally in useful ways; for example, use cases such as adding syntax highlighting, parsing and creating responsive images, embedding videos, and many others. In each of these examples, a plugin will be injected with the Markdown Abstract Syntax Tree (AST) and manipulate content based on certain node types or content in particular nodes.
- Further understanding of the remark Abstract Syntax Tree (AST)
- How to create a plugin that is injected with an AST via
- How to manipulate the remark AST to add additional functionality
There a few things that you should have some understanding with:
- How to work with Remark in Gatsby as described in Part Six and Part Seven of the Gatsby Tutorial.
- Understanding of the Markdown Syntax.
To get an understanding at what is available in the Markdown AST, take a look at the Markdown AST spec that is used in remark and other unist projects: syntax-tree/mdast.
Starting out with a Markdown file as below:
Remark would translate this into an AST made available to
gatsby-transformer-remark plugins. The AST would appear as the following:
As well, AST Explorer is a site that gives you a side-by-side view of the markdown and the outputted AST.
You are going to create a plugin that colors all top-level headings in the markdown with the color purple.
First create a local plugin by adding a
plugins folder in your site and generating a
package.json file for it. As well, create an
index.js file. In this file, we export a function that will be invoked by
The first parameter is all of the default properties that can be used in plugins (actions, store, getNodes, schema, etc.) plus a couple just for gatsby-transformer-remark plugins. The most relevant field for our purposes is the
markdownAST field which is destructured in the code snippet above.
As with other Gatsby plugins, the 2nd parameter is the
pluginOptions which is obtained from the definition in
Finally, the function will return the
markdownAST after the fields you wish to be edited are transformed.
You likely will want to grab
gatsby-source-filesystem to inject the file nodes into Gatsby’s GraphQL schema. In this example it is assumed that the Markdown files exist in a
The plugin is now initially set so you can add it as a sub-plugin inside
If you want to add some options, you could switch to the object syntax:
In case you use
gatsby-plugin-mdx in place of
gatsby-transformer-remark, the former takes an array config option named
gatsbyRemarkPlugins that allows compatibility with Gatsby’s remark plugins.
gatsby-plugin-mdx recognize a local plugin like
gatsby-remark-purple-headers, you need to point to its location in the project through
However, if the sub-plugin is published and installed via npm, simply refer to it by name as the case with using
When modifying nodes, you’ll want to walk the tree and then implement new functionality on specific nodes.
A node module to help with is unist-util-visit, a walker for
unist nodes. For reference, Unist (Unified Syntax Tree) is a standard for Markdown syntax trees and parsers that include well known parsers in the Gatsby world like Remark and MDX.
As an example from
unist-util-visit’s README file, it allows for an interface to visit particular nodes based on a particular type:
Here, it finds all text nodes and will
console.log the nodes. The second argument can be replaced with any type described in Unist’s Markdown AST (mdast) specification including types such as
image or in our usecase,
With this technique in mind, you can similarly traverse the AST from your plugin and add additional functionality, like so:
Next, by visiting all heading nodes and passing them into a transformer function, you can manipulate the particular nodes to match your use case.
Looking again at the AST node for heading:
You have context about the text as well as what depth the heading is (for instance here you have a depth of 1 which would equate to an
With the inner function of the
visit call, you parse out all of the text and if it will map to a h1, you set the type of the node to
html and set the node’s value to be some custom HTML.
A small library mdast-util-to-string by Unified was used to extract the plain text of the inner nodes. This would remove links or other types of nodes inside the heading, but given you have full access to the markdown AST, you can modify it however you wish.
Gatsby supports the usage of asynchronous behavior in plugins, and
gatsby-transformer-remark uses unified under the hood. The following example shows how the
gatsby-remark-purple-headers transformer can be converted to asynchronous by adding the
async keyword to the function declaration.
A real-world example of this would be
At this point, our plugin is now ready to be used. To see the resulting functionality, it is helpful to re-visit Part 7 of the Gatsby Tutorial to programmatically create pages from Markdown data. Once this is set up, you can examine that your plugin works as seen below based on the markdown you wrote earlier.
Note: In case you don’t see the intended effect, try wiping out the cache by running
To share this plugin with others, you can extract the plugin to its own directory outside of this site and then publish it to npm so it can be accessed both on npm and Submitted to the Plugin Library.
You just wrote a local Gatsby plugin that is a sub-plugin for
gatsby-transformer-remark that manipulates the Remark AST. You should now have a further understanding about the structure of Markdown Abstract Syntax Trees! Yay!
If you wish to see other plugins that manipulate the Remark AST, you can find them in the plugin library.