DEV Community

Cover image for Custom File Generator Tutorial
Seth Davis
Seth Davis

Posted on • Updated on

Custom File Generator Tutorial

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. 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 prompts and handlebar 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/

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:

mkdir jarvis && cd jarvis
Enter fullscreen mode Exit fullscreen mode

Initialize git repo:

git init
Enter fullscreen mode Exit fullscreen mode

Ignore node_modules:

echo "node_modules" > .gitignore
Enter fullscreen mode Exit fullscreen mode

Add a README.md:

echo "# Jarvis CLI" > README.md
Enter fullscreen mode Exit fullscreen mode

Initialize a package.json file:

npm init -y
Enter fullscreen mode Exit fullscreen mode

Modify package.json

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

"bin": {
    "jarvis": "./index.js"
},
Enter fullscreen mode Exit fullscreen mode

Change the scripts section to include a plop script:

"scripts": {
    "plop": "plop"
},
Enter fullscreen mode Exit fullscreen mode

Install plop

npm i -D plop
Enter fullscreen mode Exit fullscreen mode

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

touch index.js
Enter fullscreen mode Exit fullscreen mode

Add Plop CLI instructions (source) by copying the following code into your index.js file:

#!/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);

Enter fullscreen mode Exit fullscreen mode

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:

touch plopfile.js
Enter fullscreen mode Exit fullscreen mode

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

export default function(plop) {
    // ...
}
Enter fullscreen mode Exit fullscreen mode

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:

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

Remember when inquirer was mentioned earlier? Here's where we'll use our inquirer prompt types. We'll be needing a name for our component, so add an input prompt to our array of prompts, like this:

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

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. 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).

Your plopfile should now look like this:

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'
            }
        ]
    });
}
Enter fullscreen mode Exit fullscreen mode

Make plop-templates

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

mkdir plop-templates
Enter fullscreen mode Exit fullscreen mode

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:

mkdir ts-component
Enter fullscreen mode Exit fullscreen mode

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

touch index.ts.hbs
Enter fullscreen mode Exit fullscreen mode

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

export { default } from "./{{ pascalCase name }}";
Enter fullscreen mode Exit fullscreen mode

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:

touch "{{ pascalCase name }}.tsx.hbs"
Enter fullscreen mode Exit fullscreen mode

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

import React from 'react';

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

Enter fullscreen mode Exit fullscreen mode

Again, you'll see pascalCase added. That's a plop case modifier - 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.

Link it up

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

npm link
Enter fullscreen mode Exit fullscreen mode

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:

<cli-name> <generator-name> <component-name>
Enter fullscreen mode Exit fullscreen mode

Example:

jarvis ts-component Button
Enter fullscreen mode Exit fullscreen mode

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.

Happy hacking :)

Top comments (1)

Collapse
 
michaeltharrington profile image
Michael Tharrington

Great first post, Seth! ๐Ÿ™Œ