remark is a pluggable markdown parser and compiler. It's part of the unified so it offers a wide array of libraries to make writing plugins with the markdown AST (MDAST) more developer friendly.
We'll use unist-util-visit
to traverse heading nodes in a Markdown file and prefix all h1
s with the text "BREAKING" using a TDD workflow.
John Otander: [0:00] In order to create a Remark plugin, we'll need to initialize a project. Then we'll need to install the development dependencies. We'll install Remark, so we can run the plugin, and Jest to run our test script. Then we'll need to add our test script. Now, we'll create our fixture markdown file that we can use in our test.
[0:18] Now, we can begin our test.js file that Jest will run. First, we'll need to require fs and read in our fixture document. Then we can scaffold out our test. For this test, we'll be adding breaking in front of all h1s, so we can go ahead and insert. We also need to require Remark, so we can pass out our document. First, we'll invoke Remark, and then call processSync with our doc.
[0:43] Remark returns a virtual file and a content string. We'll need to reference contents in our test. Now, we should be able to run our test and see it fail. As we can see, the breaking is not added because we haven't written our plugin yet. It's time to begin working on our plugin. All Remark plugins first receive an options object and then the AST structure or the tree. We'll go ahead and log that for now.
[1:11] We'll also need to modify our test.js file, so we actually use the plugin. We can require it. Then call use and pass it to plugin function. We're now ready to run our tests. As we see, we've logged out the AST structure. Those are roots. That's the main part of the document heading one of depth one which means our H1, our paragraph, our heading two, and, of course, the last paragraph.
[1:40] We'll now install unist-util-visit, so that we can walk our tree structure and only find heading nodes. First, we'll require the library. Then we'll call the visit function and pass it our tree. Then specify the heading node. Then give it a call-back which will receive the node. For now, we can go ahead and just log it. Let's run our test to verify that the headings are being logged.
[2:05] As you can see, here's our heading one and our heading two. It looks good. For this plugin, we only care about headings of depth one, so we'll need to restrict our visitor. We can do this by short-circuiting if the depth is not one. Now, we can run our tests again. We'll see that there's a single heading node of depth one. Also, that there's a children's child array of text values.
[2:27] We'll need to use unist-util-visit on the heading node, as well, to visit the text values. We can do this by calling visit again with our heading node and specify the text node type. It'll give us a new node which we can go ahead and log.
[2:43] Running the yarn test again results in a single text node that's a child of our heading node. Now, we're ready to modify the value in the text node by prefixing it with breaking. Running our test one more time will result in a passing test. The breaking is being prefixed to our heading one. Let's edit our test file to logout the entire document after our plugin, to make sure everything looks right.
[3:08] Here's our document. It looks good. All together, we've written a plugin that takes the AST, and then uses unist-util-visit to find all heading nodes, and narrow them down to those of depth one. Then we traversed the heading node one level deeper to find the text node, which is what we then prefixed with breaking.
[3:27] In our test, we require our plugin, and pass it to remark calling .use, followed by processSync with our document. Lastly, we assert that our h1 has been prefixed with breaking.