Dillion Megida I'm a Frontend Engineer and Technical Writer based in Nigeria.

How to build a Markdown plugin for your Gatsby blog

7 min read 2106

Markdown Gatsby Plugin

Since the advent of Markdown, writing articles (and text in general) has taken a new turn. In previous days, you would either have to use HTML directly or were limited to the text editing options an online text editor provided. But now, any service that supports Markdown content makes writing easier.

Gatsby and several other frameworks support Markdown. These Markdown files can be used for creating web pages or blogs. Furthermore, Gatsby allows developers to create tools — called plugins — that can hook into the Markdown files and modify the outputted HTML.

In this article, you’ll learn how to build a Markdown plugin for your Gatsby blog. As an example, we’ll build a text highlighting plugin so that when it finds a specified syntax that you’ve defined around a text, it processes it into a styled HTML that displays the text as highlighted. Here’s the plugin live on my website — in particular, the text “share code and functionality”.

Note that you are not limited to the example used in this article. This article only aims to teach you how to build a Markdown plugin. You can use the knowledge from this article to build other awesome plugins and open-source it for other developers.

Gatsby and Markdown for blogs

Markdown provides a special syntax that enables easy creation of documents. The result of the Markdown content is usually HTML, but it may be different depending on the tool used.

For example, the following HTML…

<h2>I am a header</h2>
<p>I am a paragraph</p>
<code>
   <pre>
     console.log('I am JavaScript')
  </pre>
</code>
<img src='https://google.com' alt='This is not an image'>

…can be achieved with the following Markdown content:

# I am a header

I am a paragraph

```
console.log('I am JavaScript')
```

[!This is not an image](https://google.com)

Because the end result after processing the Markdown content is HTML, using Markdown becomes a seamless process for writing regular content.

Gatsby is a static site generator used for creating different web applications, including blogs. The framework supports Markdown, making it easier for developers to write blogs in Markdown files that are transformed into full-fledged pages. This article is not focused on how Gatsby creates these pages, so check out their documentation for more information.

Abstract syntax tree for Markdown

Generally, all programming languages have syntax. The syntax of any language shows how the language works and the keywords it supports. This syntax can be represented in an abstract syntax tree (AST), which shows every node captured from the source code in a tree.

We made a custom demo for .
No really. Click here to check it out.

Markdown files have their own abstract syntax tree. You can experiment with it in this live AST explorer. The tree shows what every keyword in the Markdown file means and how they are mapped to the respective HTML element.

Let’s review the following Markdown text:

# I am a header
I am a paragraph
```
console.log('I am JavaScript')
```

Now, here’s the above Markdown file’s syntax tree from the live viewer:

{
  "type": "root",
  "children": [
    {
      "type": "heading",
      "depth": 1,
      "children": [
        {
          "type": "text",
          "value": "I am a header",
        }
      ],
    },
    {
      "type": "paragraph",
      "children": [
        {
          "type": "text",
          "value": "I am a paragraph",
        }
      ],
    },
    {
      "type": "code",
      "lang": null,
      "meta": null,
      "value": "console.log('I am JavaScript')",
    }
  ],
}

Note that the first Markdown file listed above is summarized to show the important pieces of HTML, but you can find full information in the live viewer.

In this Markdown content, the tree broke down every part of it into nodes, each with different types, values, and so on.

gatsby-transformer-remark, the tool behind Markdown modifications

gatsby-transformer-remark is a plugin created by the Gatsby team. The purpose of this plugin is to parse the Markdown content into the final HTML. The plugin uses the abstract syntax tree to achieve this.

gatsby-transformer-remark receives the AST from Markdown, which allows other plugins to modify the content. Essentially, producing the final HTML is a joint effort of the plugin and gatsby-transformer-remark.

Building the text highlighting plugin

Just like every other Markdown plugin, the text highlighting plugin will hook into the Markdown file and modify some (or all) of the content into HTML, which will be included in the final HTML.

For this plugin, we want to hook into the Markdown file, grab a text or paragraph that matches a syntax we will define, and replace it with an element that contains some styles to make it highlighted.

The manual way to achieve this would be by adding the element directly into the Markdown content:

# I am a header
I want <span class='highlight'>this text</span> highlighted.

But manually adding the element into the Markdown file for every text you want to be highlighted in your article — or across several articles — can be tedious. So, why not make it easier?

In our plugin, we’ll use this syntax:

I want -# this text #- highlighted

Note that -# and #- are the start and close symbols. Here, our plugin will pick every string that matches this syntax and format it to:

I want <span class="highlight">this text</span>

or

I want <span style="...styles">this text</span>

If the class name method is used, the class name can be used in your global stylesheet. If the style method is used, it applies inline styles.

Setting up the environment

Ideally, this plugin would be a standalone project. However, we wouldn’t want to continuously deploy to npm, update the installed plugin in the project, and test until we are satisfied.

Thankfully, Gatsby allows the use of local plugins. This means that the plugin would live with a Gatsby project and we can directly test it.

If you have an already existing Gatsby blog to test this plugin on, then you’re set. If not, quickly clone this repo (Gatsby starter blog) and install the required dependencies.

The next step is to create a plugins folder at the root of the project. When Gatsby is building its files, it first checks this folder to see whether a specified plugin exists before checking node_modules.

In the plugins folder, create a new folder named after our plugin. I’m calling it gatsby-remark-text-highlighter.

In your terminal, change your current directory into this folder and run npm init. Answer the questions and you’ll have package.json created for you.

For this plugin to work, we need two dependencies: unist-util-visit and <mdast-util-to-string. The former is used to visit (hook into) all nodes, just as we saw in the abstract syntax tree, in the Markdown file, while the latter is used to get the text content of a node.

Run:

npm install unist-util-visit mdast-util-to-string --save

In Gatsby, you have to add every plugin used to gatsby-config.js. Hence:

module.exports = {
  plugins: [
    {
      resolve: `gatsby-transformer-remark`,
      options: {
        plugins: [
          {
            resolve: `gatsby-remark-text-highlighter`,
            options: {}
          },
        ]
      }
    }
  ]
}

The plugin is added as a plugin of gatsby-transformer-remark and not at the root because, as stated earlier, this plugin is powered by it.

Developing the plugin

Create index.js file and add the following:

const visit = require("unist-util-visit")
const toString = require("mdast-util-to-string")

module.exports = ({ markdownAST }, options) => {
  const {useDefaultStyles = true, className = ""} = options;

  visit(markdownAST, "paragraph", (node) => {
    // do something with paragraph
  });
}

gatsby-transformer-remark uses the function we expose from this plugin to modify the Markdown content. It passes an object full of options (we are only concerned with markdownAST) and an options (as specified in gatsby-config.js) as arguments.

From the options argument, we descontruct two properties: useDefaultStyles, which specifies if the styles created by this plugin should be used, and className, which specifies the class that will be added to the element.

With visit (exported function from unist-util-visit), we can visit all paragraphs in the markdownAST (Markdown Abstract Syntax Tree) of the Markdown file and apply a callback function. The callback function is given the node argument.

The next steps are to define the syntax. Regex would be used for the syntax so that we can select strings that match it. Here’s the regex:

const syntax = /-#.*#-/

The above regex would match every text that appears, like so:

-# The cat caught the mouse #-
I want to be -# highlighted #-. I -# mean #- it.

Putting everything together, we have:

const visit = require("unist-util-visit")
const toString = require("mdast-util-to-string")

module.exports = ({ markdownAST }, options) => {
  visit(markdownAST, "paragraph", (node) => {
    let para = toString(node)
    const syntax = /-#((?!#-).)*#-/ig
    const matches = para.match(syntax);

    if (matches !== null) {
      console.log(para);
    }
  });
}

The regex matches any string that has -# and #- without -# in between. (?!#-) will help pick out multiple instances of the highlighted words.

Since visit visits every paragraph, we need to add the clause matches !== null to ensure that we only modify paragraphs we need.

To test this, open your Gatsby blog, quickly create a new Markdown file (or an existing one) and add:

I want to be -# highlighted #-

I -# have #- different -# highlights #-

I do not want to be highlighted

Now run gatsby develop in your terminal, and you’ll see I want to be -# highlighted #- and I -# have #- different -# highlights #- logged to the terminal. Here’s a screenshot:

Gatsby Markdown highlighted text screenshot
Screenshot of texts logged to the console that match the syntax.

Now that we’ve confirmed we’re grabbing the right text, the next thing to do is format it. Here’s the rest of the code:

const visit = require("unist-util-visit")
const toString = require("mdast-util-to-string")
module.exports = ({ markdownAST }, options) => {
  const {useDefaultStyles = true, className = ""} = options;
  visit(markdownAST, "paragraph", node => {
    let para = toString(node)

    const syntax = /-#((?!#-).)*#-/ig
    const matches = para.match(syntax)

    if (matches !== null) {
      let style = null
      if (useDefaultStyles) {
        style = `
          display:inline-block;
          padding:5px;
          background-color:yellow;
          color:black;
          border-radius: 5px;
        `
      }

      // remove #- and -#
      const removeSymbols = text => text.replace(/-#/g, "").replace(/#-/g, "")

      const putTextInSpan = text =>
        `<span
          ${useDefaultStyles && style ? ` style='${style}'` : ""}
          ${className !== "" ? `class='${className}'` : ""}
        >${removeSymbols(text)}</span>`

      matches.map(match => {
        para = para.replace(match, putTextInSpan(match))
      })
      para = '<p>' + para + '</p>'
      node.type = "html"
      node.children = undefined
      node.value = para
    }
  })
  return markdownAST
}

To use the new changes added to the plugin after the last gatsby develop, you need to run gatsby clean first because Gatsby caches the plugin.

As seen in the above code:

  • The inline styles are specified if useDefaultStyles is true
  • The text that matches the syntax is placed in a span element without the surrounding symbols
  • The texts in the matches array are mapped and every text that matches the syntax is placed in a span element without symbols
  • If className is given a value, the span element receives the value as class
  • The node’s type is changed to html, children is undefined, and value is the formatted paragraph

Now run gatsby develop again. Here’s the result of the web page using the default styles:

Gatsby Markdown highlighted text
Here’s the live view of highlighted text.

Further steps

We can also apply custom styles. Extending our plugin with the options property makes it more reusable. In gatsby-config, add the following:

{
  resolve: `gatsby-remark-text-highlighter`,
  options: {
    useDefaultStyles: false,
    className: 'text-highlight'
  }

In the global stylesheet or any stylesheet attached to the blogs, you can add something similar to this:

.text-highlight {
  padding: 10px;
  border-radius: 10px;
  background-color: purple;
  color: white;
}

Deploying the plugin

You wouldn’t be able to deploy this plugin to npm because I’ve already deployed it, and because libraries must have unique names. You can choose to name yours differently or, better still, build another awesome plugin that doesn’t exist already, just like you would with other npm libraries:

npm login
npm publish

Now, your plugin can be used by any project. No one needs to create a plugins folder in their project because your plugin will be used by Gatsby from node_modules in production.

You can find the complete codes in the source code, and you’re welcome to contribute!

Conclusion

In this article, we learned what Markdown is and how Gatsby extends the power of Markdown files by allowing us to hook into and format them. We also created a text highlighting plugin that shows an ideal method in creating Markdown plugins.

The text highlighting plugin may look simple, but should provide you with enough insight to build your own plugin.

I also used the methods listed here when creating gatsby-remark-liquid-tags. Please feel free to check it out and contribute if you’d like.

You come here a lot! We hope you enjoy the LogRocket blog. Could you fill out a survey about what you want us to write about?

Which of these topics are you most interested in?
ReactVueAngularNew frameworks
Do you spend a lot of time reproducing errors in your apps?
YesNo
Which, if any, do you think would help you reproduce errors more effectively?
A solution to see exactly what a user did to trigger an errorProactive monitoring which automatically surfaces issuesHaving a support team triage issues more efficiently
Thanks! Interested to hear how LogRocket can improve your bug fixing processes? Leave your email:

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Dillion Megida I'm a Frontend Engineer and Technical Writer based in Nigeria.

Leave a Reply