Writing a plugin for gatsby-transformer-remark

June 20, 2019

TL;DR

I wrote a gatsby-transformer-remark plugin to decorate elements with correct Bulma classes and another elements.

This article sums up my process and what I learned along the way.

The code is up at GitHub.

Motivation

As I use Bulma CSS framework in this site, I want to be able to leverage the full power of the framework.

Creating these blog pages from markdown resulted in weirdly (and wrongly) formatted HTML. The headings were missing their class="title is-{depth}" attributes and <blockquote>s and <ul>s were missing their surrounding <p class="content"> tags.

A search around the internets and Gatsby plugin page resulted with zero solutions for my problem. This only meant one thing: I gotta solve it myself.

The outcome

First, let’s have a look at what I was set to achieve.

Given this markdown:

# Heading level 1

The gatsby-tranformer-remark plugin outputted this HTML:

<h1>Heading level 1</h1>

And I wanted to have the following for Bulma to style the page correctly:

<h1 class="title is-1">Heading level 1</h1>

With this problem description I fired up VSCode.

First solution

My first solution was an easy one: just use some regex to match and replace content:

const bulmafy = post => {
      let p = post.html
      let rxs = [
        { r: /<h1>/gi, s: '<h1 class="title is-1">' },
        { r: /<h2>/gi, s: '<h2 class="title is-2">' },
        { r: /<h3>/gi, s: '<h3 class="title is-3">' },
        { r: /<h4>/gi, s: '<h4 class="title is-4">' },
        { r: /<h5>/gi, s: '<h5 class="title is-5">' },
        { r: /<h6>/gi, s: '<h6 class="title is-6">' },
        { r: /<p>/gi, s: '<p class="content">' },
        {
          r: /<ul>([^]+)<\/ul>/gi,
          s: '<div class="content"><ul>$1</ul></div>',
        },
        {
          r: /<ol>([^]+)<\/ol>/gi,
          s: '<div class="content"><ol>$1</ol></div>',
        },
        {
          r: /<blockquote>([^]+)<\/blockquote>/gi,
          s: '<div class="content"><blockquote>$1</blockquote></div>',
        },
      ]
      for (let re of rxs) {
        p = p.replace(re.r, re.s)
      }
      return p
    }

While this worked okay and did what it should, it felt…wrong.

It’s kinda hacky way to solve the issue at hand and isn’t all that portable.

Attempt #2

From GatsbyJS docs I found this excellent tutorial on how to create a plugin for the gatsby-transformer-remark.

I won’t go into detail about the contents of the tutorial as one can read it by themselves, but the gist of it is “this is how you change AST to HTML”. That’s a great way to introduce one to the world of ASTs and plugins for this system, but it set me a bit of the rail. For a while I thought that making strings from AST is all I can do with this. 🤦🏽‍♂️

After a few evenings of banging my head against the wall trying to recursively render every little case (like having a link inside a block quote to render correctly) I just sort of gave up.

Then I searched around the Remark-ecosystem of thingies and found the remark-html repo that has this part:

In addition, remark-html can be told how to compile nodes through three data properties

Score! 🥳

Now I can just add new data to the nodes and let some other plugin to recursively render it.

For example, here’s block quote:

visit(markdownAST, "blockquote", (node, ancestors) => {
    const div = {
      type: "section",
      data: {
        hName: "div",
        hProperties: { class: "content" },
      },
      children: [node],
    }
    const parent = ancestors[ancestors.length - 1]
    const startIndex = parent.children.indexOf(node)
    parent.children.splice(startIndex, 1, div)
  })

It creates a new node that will be rendered as a div with attribute class="content" and the contents of the block quote node will be its children.

Then the code replaces the original block quote node with the newly created one in the tree.

What I’ve learned

I haven’t worked with ASTs all that much in the past. This project taught me the ways on thinking about trees which will definitely come in handy in future works.

Now I also have the knowhow to create a Gatsby plugin if I ever need to make a new one.

Code

You can view the source here.

I also published the plugin to npm and it looks like a few people have already installed it.

The extra

After the publish I tweeted about it

twitter 1

…and then this happened:

twitter 2

Which lead to me writing a small guide for the Gatsby docs on how to use Bulma.

Laters!

-Juho

Other writings: