Automate scaffolding processes with Plop
May 03, 2020
I recently published a post detailing how I created this blog using Gatsby to get up and running in little time. Gatsby as a static site generator does a lot of useful things for you through the rich plugin ecosystem that it has, enabling you do things like creating a blog like this one. However the process of creating a new blog post isn’t as easy as opening a new URL. You have to create the markdown files and directories yourself manually. This might be okay at first but quickly becomes monotonous, and you know what they say:
If a task doesn’t require critical thinking, doesn’t require human input, monotonous, repetitive,
Why not automate it?
This quote aligns very much with most of us developers, and that’s exactly what we are going to do! 😁
Plop is a tool that was built with the intention of solving these kinds of problems. Quoting the plop site,
Plop is a little tool that saves you time and helps your team build new files with consistency.
Plop generates code when you want, how you want, and can be changed whenever you want.
“What about yeoman?”, you might ask. Yeoman looks to be tuned towards scaffolding entire web apps as opposed to scaffolding little bits of the app. Although yeoman generators can also generate the little bits of the app as well, those come as added benefits to the full app generator. Also yeoman generators need to be used as an extra node package that would be installed as a dependency (either by publishing it or locally by linking it) before it can be used. That’s a lot more setup than is required. On the other hand, plop only really needs a plopfile.js
file at the root of your project, and for plop to be installed as a dependency.
Here I’ll describe how I used plop to automate scaffolding a new blog post.
First let’s define the specifics of what we need:
- The posts are stored in
content/blog/
with directories describing the post slug, and anindex.md
file. For example,content/blog/hello-world/index.md
You can check the previous post to see how we set this up or you can follow along using this Gatsby starter. - Each post written in markdown contains frontmatter used to describe the post and specify metadata about the post like the title, date, tags and the author. All of that data needs to be provided by the user except perhaps the date, since you would usually use today’s date there. That’s pretty much all the information we need to have a new post.
To get started with plop, make sure you install it using yarn add --dev plop
. The plop docs recommend installing plop globally in your system, but I would recommend installing it locally since it is a dependency of one project, and not necessarily required to be a global package. For this post, I would be assuming you have plop installed as a local dependency.
Now create a plopfile.js
file with the following content:
module.exports = (
/** @type {import('plop').NodePlopAPI} */
plop
) => {
plop.setHelper('slug', val => !val ? val : val.toLowerCase().replace(/[^A-Za-z0-9_]/g, '-'));
plop.setHelper('yamlCommaToList', val => !val ? val : `[ ${val.split(',').map(_ => _.trim()).join(', ')} ]`);
plop.setGenerator('post', {
description: 'New blog post',
prompts: [{
type: 'input',
name: 'postTitle',
message: 'Title of blog post:'
},{
type: 'input',
name: 'twitterHandle',
message: 'Twitter handle of author:',
},{
type: 'input',
name: 'tags',
message: 'Specify list of tags:',
}],
actions: [{
type: 'add',
path: 'content/blog/{{slug postTitle}}/index.md',
templateFile: '.plop/templates/post-md.hbs',
data: () => {
const date = new Date();
return {
todayDate: date.toISOString(),
};
},
}],
});
};
For the most part, this is straightforward what is going on here (you should check the docs for some more detailed documentation on the API). I’ll explain what is going on here.
Plopfiles are simple node “modules” that export a function. This function takes the plop
object, giving access to the plop API.
plop.setHelper()
provides an interface to register a handlebars helper. That’s right - Plop uses handlebars as the templating engine for scaffolding. Knowledge of handlebars wouldn’t be necessary for the basic scaffolding needs but for more logic or conditionals, you would need to learn a bit of the handlebars syntax. For our case though, we only need to know the {{ data }}
double syntax which is used for interpolating data in templates, and the {{ helperfn data }}
helper syntax. Here, helperfn
is a handlebars helper function that accepts data
and returns a value. This is mostly how you would encapsulate logic within your templates. We have defined two helpers: slug
which takes a string and transforms it to a human-readable, URL-compliant, kebab-case form (e.g. Hello world
becomes hello-world
), and yamlCommaToList
which takes a string containing of list of values separated by commas, and returns a valid yaml array to represent the same data in yaml format (e.g. Hello, world, Here,I,come
becomes [ Hello, world, Here, I, come ]
).
Next we define the generator using plop.setGenerator()
. We specify the name of the generator (used to specify which generator to initiate) and a configuration object. In the configuration, we specify the description
of the generator, prompts
, and actions
.
The prompts define the list of different inputs we would ask from the user when scaffolding. These prompt configs are passed to inquirer.js which plop uses to accept the user input. For a list of valid prompt options, you would need to check the docs for inquirer.js.
The actions define the list of things that plop would do with the provided input. You can find a list of the predefined actions here which do things like adding files, directories, modifying files, appending content to the end of files. You can also provide custom actions if you need them. Here, we have defined an action to add a new file in the specified path (notice we use the slug helper here to set the slug in the file path) and using a handlebars templateFile
(we will look at the content of the template shortly). We also specified a data
function. This function returns an object that is merged with the data provided by the user when rendering the template. Here we add the todayDate
data for use in the template file.
This is the content of the post-md.hbs
used as the template for the newly generated post markdown file.
---
title: {{ postTitle }}
date: {{ todayDate }}
tags: {{ yamlCommaToList tags }}
twitterHandle: '{{ twitterHandle }}'
---
New blog post begins!
That is pretty much everything you need. You can test out the plop scaffolding by running yarn plop
at the root of your project.
? Title of blog post: asdfghjk
? Twitter handle of author: imolorhe
? Specify list of tags: twitter, linkedin, social
✔ ++ /content/blog/asdfghjk/index.md
This would run the locally installed plop package. When you run plop without specifying the --plopfile
in the command, plop looks for the plopfile.js
file at the root of the project, which is where we have added ours.
It should prompt you to provide the post title, author twitter handle, and list of tags. You should then see the generated file content/blog/asdfghjk/index.md
in the project containing the rendered post template.
---
title: asdfghjk
date: 2020-05-04T00:05:04.669Z
tags: [ twitter, linkedin, social ]
twitterHandle: 'imolorhe'
---
New blog post begins!
For convenience and visibility of the feature, you can add a new script in the package.json
file to run plop. It is easier for people that are new to your project to know that you use plop for scaffolding if they see it in your list of package.json scripts.
"scripts": {
"plop": "yarn plop"
}
Conclusion
While I used plop to generate a simple file for a blog post, you can use plop for much more scaffolding with the different options available to you. The dynamic nature of the plop actions, allow for performing several other tasks, including async actions (like fetching some content from some API). Now you don’t have to keep repeating those tasks. Instead, you could spend about half an hour (or a bit more) setting up a simple plop-based process automation, sit back and enjoy! 😄
✌🏾
Did you find this useful? Do you think there are better approaches to take, or questions? You can reach out to me on twitter @imolorhe.
Write about what you learn. It pushes you to understand topics better.
Sometimes the gaps in your knowledge only become clear when you try explaining things to others. It's OK if no one reads what you write. You get a lot out of just doing it for you.
@addyosmani