Plop Micro-generator Framework
Plop is a micro-generator framework designed to streamline the creation of consistent files and code patterns within a project. It acts as 'glue code' connecting Inquirer.js prompts for user input and Handlebars.js templates for content generation. The current stable version is 4.0.5, with patch and minor releases occurring periodically to address bugs, update dependencies, and introduce performance improvements. Major versions, like v4.0.0, introduce breaking changes such as updated Node.js requirements. Plop's core differentiator is its simplicity and focus on turning best practices for file creation into easily executable terminal commands, reducing manual boilerplate and ensuring uniformity across development teams.
Common errors
-
Error [ERR_REQUIRE_ESM]: require() of ES Module ... not supported. Instead change the require of ... to a dynamic import()
cause Attempting to `require()` an ESM-formatted `plopfile.mjs` or a `.js` file treated as ESM (due to `"type": "module"` in `package.json`).fixEnsure your `plopfile`'s module type matches its export statement. For CommonJS `plopfiles`, use `module.exports`. For ESM `plopfiles`, use `export default` and potentially specify a `.mjs` extension or set `"type": "module"` in your `package.json`. -
plop: command not found
cause The `plop` executable is not in your system's PATH, either because it's not installed globally or `npx` isn't being used for a local installation.fixInstall plop globally (`npm install -g plop`) or run it using `npx plop` when installed locally within a project (`npm install --save-dev plop`). Alternatively, add a script to `package.json` like `"gen": "plop"` and execute with `npm run gen`. -
TypeError: plop.setGenerator is not a function
cause This typically happens if the function exported from `plopfile.js` does not correctly receive the `plop` object as its argument, or if the `plopfile` is malformed.fixVerify that your `plopfile` correctly exports a function that accepts `plop` as its first parameter: `export default function (plop) { /* ... */ }` (ESM) or `module.exports = function (plop) { /* ... */ }` (CommonJS).
Warnings
- breaking Plop v4.0.0 dropped support for older Node.js versions (12, 14, and 16). Projects using Plop must now run on Node.js v18 or newer.
- breaking The way `plopfile.js` files are authored depends on your project's module system. Using `export default` requires an ESM context (`.mjs` file extension or `"type": "module"` in `package.json`), while CommonJS projects must use `module.exports`.
- gotcha While Plop supports TypeScript configuration files (`plopfile.ts`), enabling this requires additional setup (e.g., `ts-node` or `tsx`) in your project for Node.js to execute them directly, or pre-compilation. Plop v4.0.2 introduced an `--init-ts` flag to help with this.
- gotcha Plop relies heavily on Handlebars.js for templating. Incorrect Handlebars syntax, missing helpers, or malformed data context can lead to unexpected output or template errors.
- gotcha To run `plop` directly from the terminal without `npx`, it's recommended to install it globally. Otherwise, you must use `npx plop` or define a `package.json` script.
Install
-
npm install plop -
yarn add plop -
pnpm add plop
Imports
- plopfile export
module.exports = function (plop) { /* ... */ }export default function (plop) { /* ... */ } - plopfile export (CommonJS)
export default function (plop) { /* ... */ }module.exports = function (plop) { /* ... */ } - nodePlop
const nodePlop = require('plop').nodePlop;import { nodePlop } from 'plop'; - PlopGeneratorConfig (Type)
import type { PlopGeneratorConfig } from 'plop';
Quickstart
import { nodePlop } from 'plop';
import path from 'path';
import fs from 'fs';
// A minimal plopfile content
const plopfileContent = `
export default function (plop) {
plop.setGenerator('component', {
description: 'Create a new React component',
prompts: [
{
type: 'input',
name: 'name',
message: 'Component name (e.g., Button):'
},
{
type: 'confirm',
name: 'hasStyle',
message: 'Include a CSS module?'
}
],
actions: [
{
type: 'add',
path: 'src/components/{{pascalCase name}}/{{pascalCase name}}.tsx',
templateFile: 'plop-templates/component.tsx.hbs',
},
{
type: 'add',
path: 'src/components/{{pascalCase name}}/index.ts',
template: 'export * from "./{{pascalCase name}}";'
},
{
type: 'add',
path: 'src/components/{{pascalCase name}}/{{pascalCase name}}.module.css',
template: '.{{camelCase name}} {\n /* styles */\n}',
skip: (data) => !data.hasStyle ? 'Skipping style file' : undefined
}
]
});
};
`;
// Create a temporary plopfile for demonstration
const tempPlopfilePath = path.join(process.cwd(), 'temp-plopfile.mjs');
const templateDir = path.join(process.cwd(), 'plop-templates');
fs.mkdirSync(templateDir, { recursive: true });
fs.writeFileSync(path.join(templateDir, 'component.tsx.hbs'), `
import React from 'react';
{{#if hasStyle}}import styles from './{{pascalCase name}}.module.css';{{/if}}
interface {{pascalCase name}}Props {
// Define props here
}
export const {{pascalCase name}}: React.FC<{{pascalCase name}}Props> = ({}) => {
return (
<div{{#if hasStyle}} className={styles.{{camelCase name}}}{{/if}}>
Hello from {{pascalCase name}}!
</div>
);
};
`);
fs.writeFileSync(tempPlopfilePath, plopfileContent);
// Programmatically run a plop generator
async function runGenerator() {
const plop = await nodePlop(tempPlopfilePath, {
cwd: process.cwd(),
dest: process.cwd()
});
const generator = plop.getGenerator('component');
// Simulate user input for component 'MyComponent' with styles
const results = await generator.runActions({
name: 'MyComponent',
hasStyle: true
});
console.log('Generator results:', results);
// Clean up temporary files
fs.unlinkSync(tempPlopfilePath);
fs.unlinkSync(path.join(templateDir, 'component.tsx.hbs'));
fs.rmdirSync(templateDir);
console.log('Generated files for MyComponent and cleaned up temporary files.');
}
runGenerator().catch(console.error);