How to really create a Gridsome remark plugin

2020 March 15

Gridsome is an interesting project but as of today the plugins are sparce and the documentation incomplete. This made it difficult to add features I missed from my Jekyll blog. In this article I will show you how to create your own Gridsome plugin. I'll focus on the creation of a remark plugin.

Don't know what UnifiedJS, Remark and Abstract Syntax Trees are ? Start by reading this article explaining what these technologies are.

The plugin functionality

For this post I want to keep things as simple as possible. So we will create a plugin to easily insert dividers in our markdown files.

When our markdown contains this --- My text the html should generate a special divider like the one below.

My text

There are multiple types of plugins in Gridsome:

  • source plugins retrieve data from the API and update the database.
  • general plugins add functionalities.
  • transformers plugins are used by source plugins to parse content.
  • plugins' plugins are plugins for other plugins.

What are set to create is a plugin's plugin. We would like to create a new functionality for the @gridsome/transformer-remark plugin.

Studying an existing plugin

I haven't found a good documentation explaining how the code should be structured. The official documentation named how to create a plugin is pretty much false advertising. But that is easily forgivable, after all the project hasn't reached v1.0.0 yet.

To get an idea of how our plugin should be structured, we take a look at the official remark-prismjs plugin. We can see that we only need an index.json file to create a plugin.

This file should export a function taking some options as a parameter and returning a function.

module.exports = (options = { transformInlineCode: true }) => tree => {
  visit(tree, 'code', (node, index, parent) => {
    parent.children.splice(index, 1, createCode(node))
  })

  if (options.transformInlineCode) {
    visit(tree, 'inlineCode', (node, index, parent) => {
      parent.children.splice(index, 1, createInlineCode(node))
    })
  }
}

In the end this is the plugin structure of a remark-plugin. I have already written an article about remark plugins so I won't get into more details here.

Plugin starter code

Before we start, you should know that the plugin's code is availble on my Github repository

The most basic plugin

We start by creating the most basic plugin project, a plugin that just logs the nodes visited. We create the project template:

git init gridsome-plugin-remark-divider
cd gridsome-plugin-remark-divider
touch index.js

We initialize a package.json by running npm init while in the project root. We can leave the default response for each answer asked. Then we create the plugin logic by putting the following code in index.js:

index.js
const visit = require('unist-util-visit')

module.exports = (options) => tree => {
  visit(tree, (node) => console.log(node.type))
}

Since we use the unist-util-visit dependency we need to install it:

npm install unist-util-visit

Setting up the tests

We will use Jest to test our plugin. We modify the file package.json to add a test script:

package.json
{
  "name": "gridsome-plugin-remark-divider",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "author": "Braincoke",
  "license": "MIT",
  "dependencies": {
    "unist-util-visit": "^2.0.2"
  }
}

And we install remark and jest as development dependencies:

npm install --save-dev jest remark

We create a folder tests and a file tests/plugin.test.js that will hold our tests. We start with a very simple test that never fails:

// Import remark to parse markdown
const remark = require('remark')
// Import our plugin
const plugin = require('..')

test('never fails', () => {
  const inputString = [
    '# Creating a Gridsome remark plugin',
    '',
    'This is very similar to creating a remark plugin',
    '',
    '---',
  ].join('\n')

  // Create our processor with our plugin
  const processor = remark()
  .use(plugin)

  const resultString = processor.processSync(inputString).toString()
  expect(true).toBe(true)
})

Testing our setup

We can now check our setup by running our tests with npm run test. Jest should execute our test, which uses our plugin to log the node types. The output should be similar to this one:

 PASS  tests/plugin.test.js
  ✓ never fails (12ms)

  console.log index.js:4
    root

  console.log index.js:4
    heading

  console.log index.js:4
    text

  console.log index.js:4
    paragraph

  console.log index.js:4
    text

  console.log index.js:4
    thematicBreak

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.774s, estimated 1s
Ran all test suites.

You can configure Visual Studio to debug Jest tests. Take a look at the Microsoft recipe or this blog post.

Developping our plugin

Divider code

When our markdown contains this --- New section the html should generate a special divider like the one below.

New section

The code to generate the divider is the following:

html
<div class="divider">My text</div>

Our styles will be stored in a file named simple_divider.css placed at the root of our project:

simple_divider.css
.divider{
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
  font-size: 1.5em;
  margin: 0 1em;
}
.divider::before{
  content: '';
  height: .125em;
  background: black;
  flex: 1;
  margin: 0 .25em 0 0;
}
.divider::after{
  content: '';
  height: .125em;
  background: black;
  flex: 1;
  margin: 0 0 0 .25em;
}

Plugin code

We get back to our index.js file and modify it with the following code:

index.js
const visit = require('unist-util-visit')

module.exports = (options) => tree => {
  // "---" followed by some text should be seen as a paragraph
  visit(tree, 'paragraph', (node) => {
    if (node.children.length==1) {
      const text = node.children[0].value
      // Does it start with --- ?
      if (text.startsWith('---')) {
        // We extract the text after ---
        const title = text.substr(3).trim()
        const embedHtml = `<div class='divider'>${title}</div>`
        // We modify the node
        node.type = 'html'
        node.value = embedHtml
        node.children = null
      }
    }
  })
}

Test code

We update our test code in tests/pluging.test.js to verify that our plugin works properly.

plugin.test.js
// Import remark to parse markdown
const remark = require('remark')
// Import our plugin
const plugin = require('..')

test('add divider', () => {
  const inputString = [
    '# Creating a Gridsome remark plugin',
    '',
    'This is very similar to creating a remark plugin',
    '',
    '--- This is a title'
  ].join('\n')

  const expectedString = [
    '# Creating a Gridsome remark plugin',
    '',
    'This is very similar to creating a remark plugin',
    '',
    "<div class='divider'>This is a title</div>",
    ""
  ].join('\n')
  // Create our processor with our plugin
  const processor = remark()
  .use(plugin)

  const resultString = processor.processSync(inputString).toString()
  expect(resultString).toEqual(expectedString)
})

If we run npm run test the test should pass. Note that we had to add an extra empty line in the expectedString to make the test work.

Testing our plugin manually

To be able to use our plugin in a Gridsome project we need to create a symbolic link in the npm global folder. We do so by running npm link in the plugin root directory.

Then we need a starter Gridsome project to test it. We create a new project from an existing starter with:

gridsome create minimal-blog https://github.com/lauragift21/gridsome-minimal-blog

Installing the plugin

To install our custom plugin, we have to use npm link instead of npm install in the gridsome project to perform the install:

cd minimal-blog
npm link gridsome-plugin-remark-divider

Configuring Gridsome

We have installed our plugin but Gridsome doesn't know how or when to use it. We update gridsome.config.js to indicate that our plugin should be used:

gridsome.config.js
transformers: {
  //Add markdown support to all file-system sources
  remark: {
    externalLinksTarget: '_blank',
    externalLinksRel: ['nofollow', 'noopener', 'noreferrer'],
    plugins: [
      'gridsome-plugin-remark-divider',
      '@gridsome/remark-prismjs'
    ]
  }
}

Adding the divider style

Our plugin contains a file simple_divider.css containing the divider style. To use it we can simply import it in src/main.js:

main.js
import DefaultLayout from '~/layouts/Default.vue'
import 'prismjs/themes/prism.css'
import 'gridsome-plugin-remark-divider/simple_divider.css'

Testing our config

Now that our plugin is configured, we modify a blog post in content/posts/posts.md and add a line with our special divider --- Hello world. We can now run gridsome develop and check that the project builds properly and displays our divider.

Publishing our plugin on npm

There are so many good resources around explaining how to publish your package on npm that I don't see the point of repeating this knowledge here. I particularly like the alligator.io blog. Check out his tutorial on how to publish a package.

Just remember that you have to be logged in to npm to publish your plugin. Login with the command npm login.

Add the keyword gridsome-plugin to appear on the Gridsome plugins page

Conclusion

Creating a Gridsome plugin for Remark is almost completely identical to creating a plugin for remark. You should know that Gridsome is inspired from Gatsby, the counterpart of Gridsome for React. Gatsby is more mature and has a lot more more plugins. If you ever need to create a plugin for Gridsome, check out Gatsby's plugins : porting the plugin should be really easy. Actually, this is what I did to create the plugin gridsome-plugin-remark-mermaid: I ported (and improved) the code from gatsby-remark-mermaid.

Resources