Likewise, we have defined a base architecture with our team that we request to maintain and that is sometimes not easy to do.
To do this, we can use a small but powerful tool called Plop.
Plop is (…) a “micro-generator framework”. Now, I call it that because it’s a little tool that gives you a simple way to generate code or any other type of plain text files consistently. See, we all create structures and patterns in our code (routes, controllers, components, helpers, etc.). These patterns change and improve over time, so when you need to create a NEW pattern, it’s not always easy to find the files in your codebase that represent the current “best practice.” That’s where Plop saves us.
#.Starting with Plop
To get started, let’s create a simple project and install Plop as a development dependency in our project.
mkdir test-plop
cd test-plop
npm init -y
npm install plop --save-devWith this, we now have plop installed in our project and we can start using it. To do this, we are going to create a file called plopfile.js in the root of our project.
module.exports = function (plop) {
plop.setGenerator('basics', {
description: 'this is a skeleton plopfile',
prompts: [],
actions: []
});
};We will also add a script in our package.json to run plop:
// ...
{
"scripts": {
"plop": "plop"
}
}
// ...And we run it just to make sure everything is working as it should:
npm run plop
plop-test@1.0.0 plop
plop#.Generating the first generator: a React component
Let’s say our team has decided to use React and has defined a base architecture for our components. In this case, we are going to create a generator that allows us to create a React component with the following structure:
- It will use the
.jsxextension. - Will use
PropTypesfor type definition. - It will have a name in PascalCase.
- It will have a directory with the same name.
- You will have a file
index.jsxthat will export the component. - It will have a file
[ComponentName].copy.jsonthat will contain the plain text used by the component. - You will have a file
[ComponentName].module.scssthat will export the component styles.
To do this, we are going to modify our plopfile.js file and add the name of our generator, a description and the questions we want to ask the user for the generation of our components.```js
module.exports = function (plop) {
plop.setGenerator(‘component’, {
description: ‘Create a new component’,
prompts: [
{
type: ‘input’,
name: ‘name’,
message: ‘What is your component name?’,
},
],
actions: [
{
type: ‘add’,
path: ‘src/components/{{pascalCase name}}/index.jsx’,
templateFile: ‘plop-templates/component/index.jsx.hbs’,
},
{
type: ‘add’,
path: ‘src/components/{{pascalCase name}}/{{pascalCase name}}.copy.json’,
templateFile: ‘plop-templates/component/copy.js.hbs’,
},
{
type: ‘add’,
path: ‘src/components/{{pascalCase name}}/{{pascalCase name}}.module.scss’,
templateFile: ‘plop-templates/component/component.module.scss.hbs’,
},
],
});
};
We see that we are referencing a directory called `plop-templates` that does not yet exist. This directory will contain the files that we will use as templates for the generation of our components. To do this, we are going to create it and add the files that we will use as templates. We will also create the directory `src/components` which will contain our components.
```shell
mkdir plop-templates
mkdir src
mkdir src/componentsAnd we create our templates or templates for the generation of our 3 templates. These templates are written in Handlebars.
// plop-templates/component/index.jsx.hbs
import React from 'react';
import PropTypes from 'prop-types';
import COPY from './{{pascalCase name}}.copy.json';
import styles from './{{pascalCase name}}.module.scss';
const {{pascalCase name}} = () => {
return (
<div className={styles.container}>
<h1>{COPY.title}</h1>
</div>
);
};
{{pascalCase name}}.propTypes = {
// property: PropTypes.string.isRequired
};
export default {{pascalCase name}};// plop-templates/component/copy.js.hbs
{
"title": "{{pascalCase name}} title"
}// plop-templates/component/component.module.scss.hbs
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
}With this, we can proceed to run our generator and see the result.
npm run plop
> plop-test@1.0.0 plop
> plop
? What is your component name? Test
✔ ++ /src/components/Test/index.jsx
✔ ++ /src/components/Test/Test.copy.json
✔ ++ /src/components/Test/Test.module.scssAnd if we explore the generated file, we will see that the name that we entered in the console has been used in the generation of our files, and using PascalCase.
// src/components/Test/index.jsx
import React from 'react';
import PropTypes from 'prop-types';
import COPY from './Test.copy.json';
import styles from './Test.module.scss';
const Test = () => {
return (
<div className={styles.container}>
<h1>{COPY.title}</h1>
</div>
);
};
Test.propTypes = {
// property: PropTypes.string.isRequired
};
export default Test;#.Next steps
With Plop we can generate any type of file, from React components to configuration files, stories or even tests.
Likewise, Plop allows the creation of custom prompts, which below use Inquirer.js. This allows us to create questions that allow us to generate files according to the answers that the user enters or even a flow of questions, validations, etc.
For example, if we wanted to add validation so that the component name contains at least 3 characters, we could do it as follows and add the following prompt, called name:
const name = {
type: 'input',
name: 'name',
message: 'What is your component name?',
validate: (input) => {
if (input.length >= 3) {
return true;
}
return 'name should be at east 3 characters long.';
},
};
module.exports = function (plop) {
plop.setGenerator("components", {
description: "generate a component",
prompts: [name],
// ... el resto quedaría igualand when we run it, we can see the new validation:
npm run plop
> plop-test@1.0.0 plop
> plop
? What is your component name? a
>> name should be at east 3 characters long.The rest would work exactly the same, only we added a new layer for validation. From then on, the sky is the limit 🌁🙌.