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.
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.
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.
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
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
.
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.
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.
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:
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:
useDefaultStyles
is true
span
element without the surrounding symbolsmatches
array are mapped and every text that matches the syntax is placed in a span element without symbolsclassName
is given a value, the span
element receives the value as classnode
’s type
is changed to html, children
is undefined, and value
is the formatted paragraphNow run gatsby develop
again. Here’s the result of the web page using the default styles:
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;
}
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!
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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowSOLID principles help us keep code flexible. In this article, we’ll examine all of those principles and their implementation using JavaScript.
JavaScript’s Date API has many limitations. Explore alternative libraries like Moment.js, date-fns, and the new Temporal API.
Explore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.