Custom File Generator Tutorial
Tags: cli, productivity, javascript, tooling
Published: 2023-09-29 13:49:01

As a developer who works on multiple React projects daily, I like having a tool that can help me quickly and efficiently write consistent code. One of the best ways I've found is writing a custom command line tool to rapidly scaffold out my most common code patterns.

My tool of choice is [Plop.js](https://plopjs.com/). Plop is a powerful "micro-generator framework" built to help maintain patterns as well as speed up your project build time. From the documenation:

>If you boil plop down to its core, it is basically glue code between [inquirer](https://github.com/SBoudrias/Inquirer.js/) prompts and [handlebar](https://github.com/wycats/handlebars.js/) templates.

In this tutorial, we'll build out a simple React component generator for your Typescript projects. By the end, you'll have a fully functioning CLI that is customized to your file generating needs. Let's get started.

## Prerequisites

You must have `node` & `npm` installed. For more information, visit [https://nodejs.org/](https://nodejs.org/)

## Setup

Here's the funnest part, you get to name your CLI! I'm going to call mine `jarvis`.

Start by creating a directory and changing into that directory:

```bash
mkdir jarvis && cd jarvis
```

Initialize git repo:

```bash
git init
```

Ignore `node_modules`:

```bash
echo "node_modules" > .gitignore
```

Add a `README.md`:

```bash
echo "# Jarvis CLI" > README.md
```

Initialize a package.json file:

```bash
npm init -y
```

## Modify `package.json`

Add a `bin` key. This will be the command line tool's name:

```json
"bin": {
    "jarvis": "./index.js"
},
```

Change the scripts section to include a `plop` script:

```json
"scripts": {
    "plop": "plop"
},
```

## Install `plop`

```bash
npm i -D plop
```

Create the index file where our plop setup is going to live:

```bash
touch index.js
```

Add Plop CLI instructions ([source](https://plopjs.com/documentation/#wrapping-plop)) by copying the following code into your `index.js` file:

```js
#!/usr/bin/env node
import path from 'node:path';
import minimist from 'minimist';
import { Plop, run } from 'plop';

const args = process.argv.slice(2);
const argv = minimist(args);

import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));

const config = {
    cwd: argv.cwd,
    configPath: path.join(__dirname, 'plopfile.js'),
    preload: argv.preload || [],
    completion: argv.completion
};

const callback = (env) => Plop.execute(env, run);

Plop.prepare(config, callback);

```
The main take away from the code above is that Plop will launch in the current working directory and it will allow you pass parameters to your command line tool (more on this later).

Time to make a plopfile - for the next few steps, I'm going to take a more granular approach. Each step will add to the same chunk of code which will end up being the full `plopfile` code. Let's make the file:

```bash
touch plopfile.js
```

For starters, you'll need to export a default function with `plop` as an argument. In `plopfile.js`, add:

```js
export default function(plop) {
    // ...
}
```

Plop has a method called `setGenerator`. It takes an array of `prompts` and an array `actions`. Based on the answers from the `prompts` you will get a customized output. Here we'll create our first generator, called `ts-component`:

```js
export default function (plop) {
    plop.setGenerator('ts-component', {
        description: 'A React component written in Typescript',
        prompts: [],
        actions: []
    });
}
```

Remember when `inquirer` was mentioned earlier? Here's where we'll use our [inquirer prompt types](https://github.com/SBoudrias/Inquirer.js#prompt). We'll be needing a `name` for our component, so add an `input` prompt to our array of prompts, like this:

```js
export default function (plop) {
    plop.setGenerator('ts-component', {
        description: 'A React component written in Typescript',
        prompts: [
            {
                type: 'input',
                name: 'name',
                message: 'Component name'
            }
        ],
        actions: []
    });
}
```

Next, we need to add an action. Plop has a few to choose from and for this tutorial, we'll be using the [`addMany` action](https://plopjs.com/documentation/#addmany). As the names suggests, it will add multiple files to the `destination` that we give it.

Add an object to the actions array, give it a `type` of `addMany`. For `destination` we'll use `${process.cwd()}/{{ pascalCase name }}`. This little string will point to the folder where the command was executed and create a folder with the `name` of your component in pascal case. The next key is the `templateFiles` which we have not created yet, go ahead and add it anyway. Lastly is the `base` key which chops the namespace to whatever you like ([see here for more](https://plopjs.com/documentation/#addmany)).

Your plopfile should now look like this:

```js
export default function (plop) {
    plop.setGenerator('ts-component', {
        description: 'A React component written in Typescript',
        prompts: [
            {
                type: 'input',
                name: 'name',
                message: 'Component name'
            }
        ],
        actions: [
            {
                type: 'addMany',
                destination: `${process.cwd()}/{{ pascalCase name }}`,
                templateFiles: 'plop-templates/ts-component',
                base: 'plop-templates/ts-component'
            }
        ]
    });
}
```

## Make plop-templates

Here is where all of our templats are going to live. From the `root`, run:

```bash
mkdir plop-templates
```

I personally like having a folder for each of my generators. That way when I use `addMany` I can have the entire folder "copied" instead of individually adding single files with the `add` action.

Inside of the `plop-templates` directory, make a `ts-component` directory:

```bash
mkdir ts-component
```

Let's make some ts-component template files. While in the `ts-component` directory, run:

```bash
touch index.ts.hbs
```

Here's our first usage of the `handlebars` ([docs](https://handlebarsjs.com/)) template. The `.hbs` extension will be removed once we run the action. Inside `index.ts.hbs`, add:

```js
export { default } from "./{{ pascalCase name }}";
```

This file is more of a personal preference, sometimes called a "barrel" file. It allows you to write imports a little cleaner. Instead of having `../components/Button/Button` be your import, you can just write `../components/Button`.

Next we're going to use a funky file name that plop will use to create our unique file name:

```bash
touch "{{ pascalCase name }}.tsx.hbs"
```

In the `{{ pascalCase name }}.tsx.hbs`, let's drop some Handlbar syntax into our component template file:

```js
import React from 'react';

export default function {{ pascalCase name }}({ children }): JSX.Element {
    return <div>{children}</div>;
}

```

Again, you'll see `pascalCase` added. That's a plop [case modifier](https://plopjs.com/documentation/#case-modifiers) - I recommend checking them out if you have different preferences.

If you'd like to see example repo, feel free to copy/clone [this example](https://github.com/sethdavis512/jarvis).

## Link it up

We are now at the point where we can link to npm globally. In the root of `jarvis` run:

```bash
npm link
```

If all goes well you should see a symlink get added to your computer and now you should be able to run `jarvis`.

You can use the `jarvis` command as is, it will prompt you for which generator (assuming you have more than one) OR you can pass args to `jarvis` in order to execute the generator instantly.

For the `ts-component` generator, the pseudo syntax would be:

```bash
<cli-name> <generator-name> <component-name>
```

Example:

```bash
jarvis ts-component Button
```

Tada! Button is ready to go!

## Alternative uses

Now that `jarvis` is working, think about the other file types you write daily. You don't just have to write Typescript files...Are you a blogger? Make a markdown template for your posts. Are you a Python dev? Have `jarvis` make you a script template.

The possibilities are limitless!

## Conclusion

Congrats on making your custom file generator. I hope this tutorial helps you in your day-to-day. Reach out if you have any questions.

- [Example repo](https://github.com/sethdavis512/jarvis)
- [sethbot](https://github.com/sethdavis512/sethbot)

Happy hacking :)