In this post join me as I create a generator for a React component. We will be using a really cool tool named Plop and in the end we will be able to spawn new components faster and better.
Introduction
If you appreciate a good Dev experience (DX) and strive to maintain good coding quality/standards, having a tool for generating components scaffold code is a must. Whether you maintain a components library in your organization on not, such a tool will enable you and your colleagues to increase your dev velocity and focus on the real important aspects of component development.
Requirements
So what are we going for in this one?
Well, our generator should create the following:
- A Component folder with the name given by the developer
- An index.jsx file for the component
- An index.css file which the index.jsx file will import with basic generic rules
- An index.test.js file with a single test that checks the component’s render
- A Storybook story file which has a single story for the component
That’s quite a lot right there. Let’s get to it
Plop
As mentioned above, one of the tools out there which can help us generate such code is Plop. I find this tool super intuitive with fairly clear documentation.
Going by the Plop documentation, let’s first install it and make sure we can use it:
yarn add plop -D
This is of course a dev dependency.
Let’s create our plopfile.js
file right out of the example, using CJS format since the project I’m implementing this for currently does not support the ESM format:
module.exports = function (plop) {
// create your generators here
plop.setGenerator('basics', {
description: 'this is a skeleton plopfile',
prompts: [], // array of inquirer prompts
actions: [], // array of actions
});
};
Before we pour more content to that generator, let’s see if Plop launches as expected by adding an npm script to our package.json
and calling it:
"scripts": {
. . .
"plop": "plop"
},
Running yarn plop
and… nothing happens. We need to introduce a generator. I’m going back to my plopfile.js
and adding this:
plop.setGenerator('React component generator', {
description: 'A generator for React components',
prompts: [
{
type: 'input',
name: 'name',
message: 'Component name',
},
],
});
Running plop again and I get this nice prompt asking for the component’s name. I gave it the name “Matti” but got this error:
True. Let’s add an action.
In our action I would like to create a directory by the given component’s name, and set an empty index.jsx file in it (for now). My generator looks like this now:
plop.setGenerator('React component generator', {
description: 'A generator for React components',
prompts: [
{
type: 'input',
name: 'name',
message: 'Component name',
},
],
actions: [
{
type: 'add',
path: 'src/{{name}}/index.jsx',
},
],
});
Very intuitive IMO.
So now I have an index.jsx file residing under the Matti
directory, which is under the src
directory. Perfect.
Let’s fill up the content for this file. In order to do that we’re going to use a Handlebars (yes, an .hbs file, you heard right) template which will allow us to create the content according the component name we gave earlier -
I’m creating a plop-templates/react-component/index.hbs
under the root of the project and put the minimal code I need to create a React component:
import React from 'react';
import PropTypes from 'prop-types';
const MyComponent = (props)=> {
return <div>MyComponent {props.sampleProp}</div>
}
MyComponent.propTypes = {
sampleProp: PropTypes.number,
};
export default MyComponent;
Notice that I’m not using any dynamic fields in that template yet
Now I will tell the action previously made to use this template when creating a component:
actions: [
{
type: 'add',
path: 'src/{{name}}/index.jsx',
templateFile: 'plop-templates/react-component/index.hbs',
},
],
Let’s run Plop and see what we get.
Yes, the file is created with the expected content (BTW if the file already exists it will alert on that and will not override it).
It is time to use the given component name inside the template:
import React from 'react';
import PropTypes from 'prop-types';
const {{name}} = (props)=> {
return <div>{{name}} {props.sampleProp}</div>
}
{{name}}.propTypes = {
sampleProp: PropTypes.number,
};
export default {{name}};
Much better! After generating the component now the index.jsx
file is set correctly.
Checking real fast what we have so far - A Component folder with the name given by the developer and an index.jsx file for the component. Great, moving on to creating this index.css
file for this component
I would like the index.css
file to contain the class name according to the given component’s name, but in lowercase and for that it would be nice to introduce a helper function which Handlebars can use.
In the plopfile.js
I add this:
// Helpers
plop.setHelper('toLowerCase', function (text) {
return text.toLowerCase();
});
Now I will create the index.css
template under plop-templates/react-component/index.css.hbs
with the following content:
.{{toLowerCase name}} {
color: aqua;
}
Having the .css
template I would like the plop action to generate it along with the index.jsx
file. How do I do that?
This requires us to use another Plop api called “addMany” and to change the index.jsx
template names a bit. Let’s start first with changing the template name and you will soon understand why -
index.hbs
is renamed to index.jsx.hbs
Going to the plopfile, let’s use the “addMany” action like this:
actions: [
{
type: 'addMany',
destination: 'src/{{name}}',
base: `plop-templates/react-component/`,
templateFiles: 'plop-templates/react-component/*',
},
],
You might be wondering what’s going on here (and the docs are not really clear on that)
The “destination” is where we want all our files to be generated.
The “templateFiles” (note the little “s” at the end) is where all the templates for this action reside. It was a good thing there to create a directory for each generator.
The “base” is the part we would like to remove from the final file names.
Plop knows to remove the “.hbs” and the base from the final file name and this is why we changed the name of the index file.
Let’s do some little modifications to the index.js.hbs
template so that our component will import and use the index.css
generated for it:
import React from 'react';
import PropTypes from 'prop-types';
import './index.css';
const {{name}} = (props)=> {
return <div className="{{toLowerCase name}}">{{name}} {props.sampleProp}</div>
}
{{name}}.propTypes = {
sampleProp: PropTypes.number,
};
export default {{name}};
Awesome! We have a generated css file with the component imports and uses. Moving on to the test file.
We would like to create a Jest test file which will test the minimal component rendering.
We start by creating a index.test.hbs
template at the same location of the rest of the templates:
import React from 'react';
import {render, screen} from '@testing-library/react';
import {{name}} from '.';
describe('{{name}} component', () => {
it('should render', () => {
const mockSampleProp = 5;
const textQuery = `{{name}} ${mockSampleProp}`
render(<{{name}} sampleProp={mockSampleProp}/>);
expect(screen.getByText(textQuery)).toBeInTheDocument();
});
});
We do not need to change anything in our plopfile. This template will be detected and the test file will be generated.
Running Jest to make sure it all passes and indeed - we have success :)
Moving on to the Storybook story, well… you get the drill of it. I’m creating a templated called index.stories.jsx.hbs
with the following content:
import React from 'react';
import {{name}} from './index.jsx';
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
export default {
title: 'Components/{{name}}',
component: {{name}},
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
argTypes: {},
};
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Template = (args) => <div><{{name}} {...args} /></div>;
export const Simple = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Simple.args = {
sampleProp:5,
};
Running plop again and the story file is created and displayed on Story book -
What a marvelous component!
And that, my friends, is it :)
To sum up…
We now have a React component generator which creates 4 files for us with the given component’s name. Any developer can now create a component and get started in a matter of seconds.
Aside from speeding up the component creation process and allowing better DX, this helps to align the components standards in a large organization.
The code can be found on my Pedalboard monorepo code.
As always if you have any question or suggestions on how this can be done better, be sure to leave them in the comments below :)
Hey! If you liked what you've just read check out @mattibarzeev on Twitter 🍻
Photo by KOBU Agency on Unsplash
Top comments (2)
wow, slick!
Cheers mate :)