earthtwittergithubnpmlink

Getting Started

plop v1.8.0

What is Plop?

Plop is a what I like to call a "micro-generator framework." Now, I call it that because it is a small tool that gives you a simple way to generate code or any other type of flat text files in a consistent way. You 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 insert-name-of-pattern-here, it's not always easy to locate the files in your codebase that represent the current "best practice." That's where plop saves you. With plop, you have your "best practice" method of creating any given pattern in CODE. Code that you can easily engage from the terminal by simply typing plop. Not only does this save you from hunting around in your codebase for the right files to copy, but it also turns "the right way" into "the easiest way" to make new files.

If you boil plop down to its core, it is basically glue code between inquirer prompts and handlebar templates.

This documentation is a work in progress. If you have great ideas, I'd love to hear them.

Installation

1. Add plop to your project

$ npm install --save-dev plop
$ npm install -g plop

3. Create a plopfile.js at the root of your project

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
    });
};

Your First Plopfile

A plopfile starts its life as a lowly node module that exports a function that accepts the plop object as its first parameter.

module.exports = function (plop) {};

The plop object exposes the plop api object which contains the setGenerator(name, config) function. This is the function that you use to (wait for it) create a generator for this plopfile. When plop is run from the terminal in this directory (or any sub-directory), a list of these generators will be displayed.

Let's try setting up a basic generator to see how that looks.

module.exports = function (plop) {
    // controller generator
    plop.setGenerator('controller', {
        description: 'application controller logic',
        prompts: [{
            type: 'input',
            name: 'name',
            message: 'controller name please'
        }],
        actions: [{
            type: 'add',
            path: 'src/{{name}}.js',
            templateFile: 'plop-templates/controller.hbs'
        }]
    });
};

The controlller generator we created above will ask us 1 question, and create 1 file. This can be expanded to ask as many questions as needed, and create as many files as needed. There are also additional actions that can be used to alter our codebase in different ways.

CLI Usage

Once plop is installed, and you have created a generator, you are ready to run plop from the terminal. Running plop with no parameters will present you with a list of generators to pick from. You can also run plop [generatorName] to trigger a generator directly.

Why Generators?

Because when you create your boilerplate separate from your code, you naturally put more time and thought into it.

Because saving your team (or yourself) 5-15 minutes when creating every route, component, controller, helper, test, view, etc... really adds up.

Because context switching is expensive and saving time is not the only benefit to automating workflows

Plopfile Api

The plopfile api is the collection of methods that are exposed by the plop object. Most of the work is done by setGenerator but this section documents the other methods that you may also find useful in your plopfile.

Main Methods

These are the methods you will commonly use when creating a plopfile. Other methods that are mostly for internal use are list in the other methods section.

MethodParametersReturnsDescription
setGeneratorString, GeneratorConfigGeneratorConfigsetup a generator
setHelperString, Functionsetup handlebars helper
setPartialString, Stringsetup a handlebars partial
setActionTypeString, CustomActionregister a custom action type
setPromptString, InquirerPromptregisters a custom prompt type with inquirer
loadArray[String], Object, Objectloads generators, helpers and/or partials from another plopfile or npm module

setHelper

setHelper directly corresponds to the handlebars method registerHelper. So if you are familiar with handlebars helpers, then you already know how this works.

module.exports = function (plop) {
    plop.setHelper('upperCase', function (text) {
        return text.toUpperCase();
    });

    // or in es6/es2015
    plop.setHelper('upperCase', (txt) => txt.toUpperCase());
};

setPartial

setPartial directly corresponds to the handlebars method registerPartial. So if you are familiar with handlebars partials, then you already know how this works.

module.exports = function (plop) {
    plop.setPartial('myTitlePartial', '<h1>{{titleCase name}}</h1>');
    // used in template as {{> myTitlePartial }}
};

setActionType

setActionType allows you to create your own actions (similar to add or modify) that can be used in your plopfiles. These are basically highly reusable custom action functions.

FunctionSignature Custom Action

ParametersTypeDescription
1) answersObjectAnswers to the generator prompts
2) configActionConfigThe object in the "actions" array for the generator
3) plopPlopfileApiThe plop api for the plopfile where this action is being run
module.exports = function (plop) {

    plop.setActionType('doTheThing', function (answers, config, plop) {
        // do something
        doSomething(config.configProp);
        // if something went wrong
        throw 'error message';
        // otherwise
        return 'success status message';
    });

    // or do async things inside of an action
    plop.setActionType('doTheAsyncThing', function (answers, config, plop) {
        // do something
        return new Promise((resolve, reject) => {
            if (success) {
                resolve('success status message');
            } else {
                reject('error message');
            }
        });
    });

    // use the custom action
    plop.setGenerator('test', {
        prompts: [],
        actions: [{
            type: 'doTheThing',
            configProp: 'available from the config param'
        }, {
            type: 'doTheAsyncThing',
            speed: 'slow'
        }]
    });
};

setPrompt

Inquirer provides many types of prompts out of the box, but it also allows developers to build prompt plugins. If you'd like to use a prompt plugin, you can register it with setPrompt. For more details see the Inquirer documentation for registering prompts. Also check out the plop community driven list of custom prompts.

const promptDirectory = require('inquirer-directory');
module.exports = function (plop) {
    plop.setPrompt('directory', promptDirectory);
    plop.setGenerator('test', {
        prompts: [{
            type: 'directory',
            ...
        }]
    });
};

setGenerator

The config object needs to include prompts and actions (description is optional). The prompts array is passed to inquirer. The actions array is a list of actions to take (described in greater detail below)

Interface GeneratorConfig

PropertyTypeDefaultDescription
description[String]short description of what this generator does
promptsArray[InquirerQuestion]questions to ask the user
actionsArray[ActionConfig]actions to perform

If your list of actions needs to be dynamic, take a look at using a dynamic actions array.

Interface ActionConfig

The following properties are the standard properties that plop handles internally. Other properties will be required depending on the type of action. Also take a look at the built-in actions.

PropertyTypeDefaultDescription
typeStringthe type of action (add, modify, addMany, etc)
abortOnFailBooleantrueif this action fails for any reason abort all future actions

Instead of an Action Object, a function can also be used

Other Methods

MethodParametersReturnsDescription
getHelperStringFunctionget the helper function
getHelperListArray[String]get a list of helper names
getPartialStringStringget a handlebars partial by name
getPartialListArray[String]get a list of partial names
getActionTypeStringCustomActionget an actionType by name
getActionTypeListArray[String]get a list of actionType names
getGeneratorStringGeneratorConfigget the GeneratorConfig by name
getGeneratorListArray[Object]gets an array of generator names and descriptions
setPlopfilePathStringset the plopfilePath value which is used internally to locate resources like template files
getPlopfilePathStringreturns the absolute path to the plopfile in use
getDestBasePathStringreturns the base path that is used when creating files
setDefaultIncludeObjectObjectsets the default config that will be used for this plopfile if it is consumed by another plopfile using plop.load()
getDefaultIncludeStringObjectgets the default config that will be used for this plopfile if it is consumed by another plopfile using plop.load()
renderStringString, ObjectStringRuns the first parameter (String) through the handlebars template renderer using the second parameter (Object) as the data. Returns the rendered template.

Built-In Actions

There are several types of built-in actions you can use in your GeneratorConfig. You specify which type of action (all paths are based on the location of the plopfile), and a template to use.

Add

The add action is used to (you guessed it) add a file to your project. The path property is a handlebars template that will be used to create the file by name. The file contents will be determined by the template or templateFile property.

PropertyTypeDefaultDescription
pathStringa handlebars template that (when rendered) is the path of the new file
templateStringa handlebars template that should be used to build the new file
templateFileStringa path a file containing the template
abortOnFailinherited from ActionConfig

AddMany

The addMany action can be used to add multiple files to your project with a single action. The destination property is a handlebars template that will be used to identify the folder that the generated files should go into. The base property can be used to alter what section of the template paths should be omitted when creating files. The paths located by the templateFiles glob can use handlebars syntax in their file/folder names if you'd like the added file names to be unique (example: {{ dashCase name }}.spec.js).

PropertyTypeDefaultDescription
destinationStringa handlebars template that (when rendered) is the destination folder for the new files
baseStringthe section of the path that should be excluded when adding files to the destination folder
templateFilesGlobglob pattern that matches multiple template files to be added
abortOnFailinherited from ActionConfig

Modify

The modify action will use a pattern property to find/replace text in the file located at the path specified. More details on modify can be found in the example folder.

PropertyTypeDefaultDescription
pathStringhandlebars template that (when rendered) is the path of the file to be modified
patternRegExpregular expression used to match text that should be replaced
templateStringhandlebars template that should replace what was matched by the pattern. capture groups are available as $1, $2, etc
templateFileStringpath a file containing the template
abortOnFailinherited from ActionConfig

Custom (Action Function)

The Add and Modify actions will take care of almost every case that plop is designed to handle. However, plop does offer custom action functions for the node/js guru. A custom action function is a function that is provided in the actions array.

See the example plopfile for a sample synchronous custom action.

Built-In Helpers

There are a few helpers that I have found useful enough to include with plop. They are mostly case modifiers, but here is the complete list.

Case Modifiers

Other Helpers

Taking it Further

There is not a lot needed to get up and running on some basic generators. However, if you want to take your plop-fu futher, read on young padawan.

Using a Dynamic Actions Array

Alternatively, the actions property of the GeneratorConfig can itself be a function that takes the answers data as a parameter and return the actions array.

This allows you to adapt the actions array based on provided answers:

module.exports = function (plop) {
    plop.setGenerator('test', {
        prompts: [{
            type: 'confirm',
            name: 'wantTacos',
            message: 'Do you want tacos?'
        }],
        actions: function(data) {
            var actions = [];

            if(data.wantTacos) {
                actions.push({
                    type: 'add',
                    path: 'folder/{{dashCase name}}.txt',
                    templateFile: 'templates/tacos.txt'
                });
            } else {
                actions.push({
                    type: 'add',
                    path: 'folder/{{dashCase name}}.txt',
                    templateFile: 'templates/burritos.txt'
                });
            }

            return actions;
        }
    });
};