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.
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.
There are multiple types of plugins in Gridsome:
What are set to create is a plugin's plugin. We would like to create a new functionality for the @gridsome/transformer-remark
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.
Before we start, you should know that the plugin's code is availble on my Github repository
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
:
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
We will use Jest to test our plugin. We modify the file package.json
to add a test script:
{
"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)
})
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.
When our markdown contains this --- New section
the html should generate a special divider like the one below.
The code to generate the divider is the following:
<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:
.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;
}
We get back to our index.js
file and modify it with the following code:
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
}
}
})
}
We update our test code in tests/pluging.test.js
to verify that our plugin works properly.
// 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.
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
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
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:
transformers: {
//Add markdown support to all file-system sources
remark: {
externalLinksTarget: '_blank',
externalLinksRel: ['nofollow', 'noopener', 'noreferrer'],
plugins: [
'gridsome-plugin-remark-divider',
'@gridsome/remark-prismjs'
]
}
}
Our plugin contains a file simple_divider.css
containing the divider style.
To use it we can simply import it in src/main.js
:
import DefaultLayout from '~/layouts/Default.vue'
import 'prismjs/themes/prism.css'
import 'gridsome-plugin-remark-divider/simple_divider.css'
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.
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
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.