Building your own component generator

2 mins |

December 20, 2020

I love to automate any repetitive task 😍. That is what excites me in programming. Sometimes I would spend more time in creating the automation than I would use it for. For any large project, I would come up with a template of how the components should look like. Then I would create a tool to generate those templates. It would give consistency across the team and save some time.

Today will show you how to build one such tool.

Component generator in action

This CLI dev tool, we should be able to create a component. We should be able to customize the generated component with some options.

Let’s list down the features we would like in this CLI tool.

What we want?

  • Add options to name the component
  • Option add files like utility, test files
  • Option to choose the location where the component would be created
  • Ability to create the component from anywhere

Let’s start by creating a component. For templating, we will be using handlebars and for CLI commands we will be using Plop.

To start with you can add this in any existing project or you can create a new one by npm init. Create a folder called src and create a file named index.js in it. Also, create a folder named templates in src.

Add plop as a dev dependency, npm install plop -D.

To run the component generator, we will add a npm script to in package.json. "create-component": "plop --plopfile src/index.js".

Let’s us go through a bunch of commands for the same

1npm init -y # if you don't have a project.
2
3mkdir mkdir src templates
4touch src/index.js

and in package.json:

1"scripts": {
2 "create-component": "plop --plopfile src/index.js"
3 },

While we are at it, let’s create a template file templates/component.hbs. And the content can be

1import React from 'react';
2import PropTypes from 'prop-types';
3
4function {{ properCase name }}({ children }) {
5 return <div>{children}</div>;
6}
7
8{{ properCase name }}.propTypes = {
9 children: PropTypes.node,
10};
11
12{{ properCase name }}.defaultProps = {
13 children: null,
14};
15
16export default {{ properCase name }};

If you notice we have used name and properCase. Here name is a variable and properCase is a built in helpers provided by plop.

So let’s create a plopfile where we ask our user to provide name.

1const componentGenerator = {
2 description: "Add a React component",
3 prompts: [
4 {
5 type: "input",
6 name: "name",
7 message: "What should it be called?",
8 default: "Button",
9 },
10 ],
11 actions: data => {
12 const actions = [
13 {
14 type: "add",
15 path: `{{properCase name}}/{{properCase name}}.component.jsx`,
16 templateFile: "../templates/component.hbs",
17 abortOnFail: true,
18 },
19 ]
20
21 return actions
22 },
23}
24
25module.exports = plop => {
26 plop.setGenerator("component", componentGenerator)
27}

Here we are creating a prompt What should it be called? and an action to create file.

So lets run this, npm run create-component.

If everything goes well, it should have asked you What should it be called? If you provide the name of the component, it create a folder named after the component and file inside that. Yay 🥳!!!

Let’s add some more features, like creating an utility file.

1const componentGenerator = {
2 description: "Add a React component",
3 prompts: [
4 {
5 type: "input",
6 name: "name",
7 message: "What should it be called?",
8 default: "Button",
9 },
10 {
11 type: "confirm",
12 name: "wantUtils",
13 default: true,
14 message: "Do you want utils?",
15 },
16 ],
17 actions: data => {
18 const actions = [
19 {
20 type: "add",
21 path: `{{properCase name}}/{{properCase name}}.component.jsx`,
22 templateFile: "../templates/component.hbs",
23 abortOnFail: true,
24 },
25 ]
26
27 if (data.wantUtils) {
28 actions.push({
29 type: "add",
30 path: `${data.componentDir}/{{properCase name}}/{{properCase name}}.utilities.js`,
31 templateFile: "./templates/component.utilities.hbs",
32 abortOnFail: true,
33 })
34 }
35
36 return actions
37 },
38}
39
40module.exports = plop => {
41 plop.setGenerator("component", componentGenerator)
42}

Before we run the CLI again by npm run create-component, we have to create the template template/component.utilities.hbs. I have kept the file empty for now. Similarly we can prompt if we need test files and add options for that as well. You may want add more options based on the project.

If you have noticed the component is always created at src, we might want to create component any directory.

We can add inquirer-select-directory as dependency for the ability to select the directory.

1const promptDirectory = require("inquirer-select-directory")
2
3const componentGenerator = {
4 description: "Add an React component",
5 prompts: [
6 // ...
7 {
8 type: "directory",
9 name: "componentDir",
10 message: "Where you like to put this component?",
11 basePath: "./",
12 },
13 ],
14 actions: data => {
15 data.username = gitUserName
16 data.createdTime = new Date().toDateString()
17 const actions = [
18 //...
19 {
20 type: "add",
21 path: `${data.componentDir}/{{properCase name}}/{{properCase name}}.component.jsx`,
22 templateFile: "./templates/component.hbs",
23 abortOnFail: true,
24 },
25 ]
26
27 return actions
28 },
29}
30
31module.exports = plop => {
32 plop.setPrompt("directory", promptDirectory)
33 plop.setGenerator("component", componentGenerator)
34}

Here we are populating the location at componentDir and using it while creating the component. Isn’t that awesome? 🕺

We have to select the directory where we want the component to be generated every time. It would be awesome if we can run this command from any location in the filesystem like gen MyAwesomeButton.

Let’s do that!

First we would have to create an executable script in bin/index.js.

1#!/usr/bin/env node
2
3const path = require("path")
4
5const plopFilePath = path.join(__dirname, "../src/index.js")
6process.argv.push("--plopfile", plopFilePath)
7const args = process.argv.slice(2)
8const { Plop, run } = require("plop")
9const argv = require("minimist")(args)
10
11Plop.launch(
12 {
13 cwd: argv.cwd,
14 configPath: argv.plopfile,
15 require: argv.require,
16 completion: argv.completion,
17 },
18 run
19)

We have used minimist to extract arguments from the command. You also add it by npm install minimist -D.

And in package.json let add this

1"scripts": {
2 "create-component": "plop --plopfile src/index.js"
3 },
4 "bin": {
5 "gen": "bin/index.js"
6 },

To make this available everywhere in yout system run npm link .

Voilà!!! 🥳🎊

I have created everything we did in a repo, component-generator.


Got a Question? Bala might have the answer. Get them answered on #AskBala

Edit on Github

Subscribe Now! Letters from Bala

Subscribe to my newsletter to receive letters about some interesting patterns and views in programming, frontend, Javascript, React, testing and many more. Be the first one to know when I publish a blog.

No spam, just some good stuff! Unsubscribe at any time

Written by Balavishnu V J. Follow him on Twitter to know what he is working on. Also, his opinions, thoughts and solutions in Web Dev.