hygen, plop, and yeoman-generator are command-line tools designed to automate repetitive code generation tasks in frontend development workflows. They help teams enforce consistent patterns, reduce boilerplate, and accelerate feature development by generating files from templates. While all three serve the scaffolding purpose, they differ significantly in architecture, complexity, and integration style.
All three tools — hygen, plop, and yeoman-generator — aim to eliminate repetitive coding through automation. But they target different use cases, from quick file generation to full project bootstrapping. Let’s compare them in real-world scenarios.
hygen treats generators as plain files in a _templates directory. No JavaScript logic is required unless you need dynamic behavior.
# Typical hygen structure
_templates/
component/
new.ejs.t
prompt.js
Template files use .ejs.t extension, and prompts are optional JavaScript files. This makes it easy to version, share, or copy generators.
plop requires a plopfile.js at your project root, where you define generators as JavaScript objects with prompts and actions.
// plopfile.js
module.exports = function (plop) {
plop.setGenerator('component', {
description: 'Create a new React component',
prompts: [{ type: 'input', name: 'name', message: 'Component name?' }],
actions: [{
type: 'add',
path: 'src/components/{{name}}/{{name}}.tsx',
templateFile: 'plop-templates/component.hbs'
}]
});
};
Everything is code-driven, giving you full programmatic control.
yeoman-generator is part of the larger Yeoman ecosystem. You write a subclass of Generator and publish it as a separate npm package (e.g., generator-myapp). Users run it via the global yo command.
// generators/app/index.js
const Generator = require('yeoman-generator');
class MyGenerator extends Generator {
async prompting() {
this.answers = await this.prompt([
{ type: 'input', name: 'name', message: 'Project name?' }
]);
}
writing() {
this.fs.copyTpl(
this.templatePath('component.tsx'),
this.destinationPath(`src/${this.answers.name}.tsx`),
this.answers
);
}
}
module.exports = MyGenerator;
This approach is powerful but heavyweight — best for creating entire projects, not individual files.
hygen uses EJS for templates, which supports embedded JavaScript logic directly in the template.
<%# _templates/component/new.ejs.t_ %>
---
to: src/components/<%= name %>/<%= name %>.tsx
---
import React from 'react';
const <%= name %> = () => <div>Hello</div>;
export default <%= name %>;
The front matter (between ---) defines metadata like output path.
plop uses Handlebars, which is logic-less by design. Complex logic must go into helpers or transforms in plopfile.js.
{{! plop-templates/component.hbs }}
import React from 'react';
const {{pascalCase name}} = () => <div>Hello</div>;
export default {{pascalCase name}};
Note the use of built-in pascalCase helper. Custom helpers can be added via plop.setHelper().
yeoman-generator also uses EJS by default, but you can configure other engines. Templates live in a templates/ folder relative to the generator.
<%# templates/component.tsx %>
import React from 'react';
const <%= name %> = () => <div>Hello</div>;
export default <%= name %>;
The rendering is handled by this.fs.copyTpl(), which injects answers into the template.
hygen is invoked directly via CLI: npx hygen component new --name Button. It doesn’t require a config file and works out of the box with its _templates folder. Great for quick adoption in existing repos.
plop is typically run as an npm script: npm run gen -- component Button. Since it’s a dev dependency, it’s versioned with your project — ideal for team consistency in monorepos.
yeoman-generator requires installing both yo globally and your specific generator (e.g., npm install -g generator-react-app). Then you run yo react-app. This global dependency model is less common in modern frontend workflows, where local tooling is preferred.
All three support interactive prompts via Inquirer.js.
hygen uses a prompt.js file that exports a function returning Inquirer questions:
// _templates/component/prompt.js
module.exports = [
{ type: 'input', name: 'name', message: 'Component name?' }
];
plop defines prompts inline in the plopfile.js:
prompts: [{ type: 'input', name: 'name', message: 'Component name?' }]
yeoman-generator uses the prompting() lifecycle method:
async prompting() {
this.answers = await this.prompt([
{ type: 'input', name: 'name', message: 'Component name?' }
]);
}
Functionally similar, but Yeoman offers more lifecycle hooks (e.g., initializing, installing), which is overkill for simple file generation.
yeoman-generator if you only need to add files to an existing project. Its global install model and project-generation focus make it awkward for incremental scaffolding.plop if you dislike maintaining JavaScript config files or need something zero-config. Its power comes with verbosity.hygen if you need complex conditional logic in templates or advanced file transforms (e.g., modifying existing files). It’s optimized for adding files, not editing them.| Feature | hygen | plop | yeoman-generator |
|---|---|---|---|
| Primary Use Case | Add files to existing repo | Add files with custom logic | Generate entire new projects |
| Config Style | File-based (no JS needed) | Programmatic (plopfile.js) | Class-based generator module |
| Template Engine | EJS | Handlebars | EJS (configurable) |
| Installation | Local or global | Local (dev dependency) | Global (yo + generator pkg) |
| Complexity | Low | Medium | High |
| Best For | Quick, consistent snippets | Team-enforced patterns in monorepos | Public starter kits or CLIs |
hygen is fast, simple, and gets out of your way.plop gives you the control and versioning you need.create-react-app)? → yeoman-generator is still viable, but consider modern alternatives like create-* scripts or Vite templates instead.In most day-to-day frontend workflows, hygen or plop will serve you better than Yeoman — they’re lighter, local-first, and integrate seamlessly with modern toolchains.
Choose hygen if you want a lightweight, fast, and file-based scaffolding tool that integrates easily into existing projects without heavy configuration. It’s ideal for teams that prefer convention over configuration and need to generate components, pages, or modules with minimal setup. Its template system uses EJS and supports prompts, making it suitable for straightforward, repeatable code generation tasks.
Choose plop if you need a flexible, programmatic generator that lives inside your project as a dev dependency and can be tightly coupled with your build or CI pipeline. It’s well-suited for monorepos or complex applications where generators must be versioned alongside source code. Plop’s API gives you full control over prompts, actions, and file transforms using JavaScript functions.
Choose yeoman-generator if you’re building a full-fledged project generator (like a starter kit or framework CLI) that needs rich interactivity, extensive prompting, and ecosystem integration via Yeoman’s registry. However, note that Yeoman is heavier and more complex; it’s best reserved for creating entire applications from scratch rather than adding files to an existing codebase.
hygen is the simple, fast, and scalable code generator that lives in your project.
New in hygen v4.0.0: a positional
NAMEparameter. Now you can use$ hygen component new MyComponentinstead of$ hygen component new --name MyComponent.
Hygen can be used to supercharge your workflow with Redux, React Native, Express and more, by allowing you avoid manual work and generate, add, inject and perform custom operations on your codebase.
If you're on macOS and have Homebrew:
$ brew tap jondot/tap
$ brew install hygen
If you have node.js installed, you can install globally with npm (or Yarn):
$ npm i -g hygen
If you like a no-strings-attached approach, you can use npx without installing globally:
$ npx hygen ...
For other platforms, see releases.
Initialize hygen in your project (do this once per project):
$ cd your-project
$ hygen init self
Build your first generator, called mygen:
$ hygen generator new mygen
Loaded templates: _templates
added: _templates/mygen/new/hello.ejs.t
Now you can use it:
$ hygen mygen new
Loaded templates: _templates
added: app/hello.js
You've generated content into the current working directory in app/. To see how the generator is built, look at _templates (which you should check-in to your project from now on, by the way).
You can build a generator that uses an interactive prompt to fill in a variable:
$ hygen generator with-prompt mygen
Loaded templates: _templates
added: _templates/mygen/with-prompt/hello.ejs.t
added: _templates/mygen/with-prompt/prompt.js
Done! Now let's use mygen:
$ hygen mygen with-prompt
? What's your message? hello
Loaded templates: _templates
added: app/hello.js
Want to start from a repo?
$ hygen init repo antfu/vitesse --to my-folder
Want to start from an existing repo on an existing project?
$ mkdir your-project && cd your-project
$ hygen init repo antfu/vitesse
Go to the documentation to get to know the rest of Hygen and generators.
If you're in a hurry:
hygen is a scalable generator. It has developer and team ergonomics as first priority.
It avoids "blessed" or dedicated projects that codifies code generation, which before you know it, become a product you build, needs testing, CI, separate pull request reviews, and ultimately sucks up your time and becomes this super hated thing no one likes to touch anymore.
Plus, since they are not the product you are building they become notoriously hard to reason about.
Because hygen templates live in your project, it cuts the time from having an itch for generating code (Redux, anyone?) in your current
project to code generated with it and others benefiting from it.
hygen picks up a local _templates folder
at any folder level of your project you're working from.
This is important because:
And yet, if you don't like project-local templates:
_templates folder (maybe a central git repo you maintain?) by populating an environment variable HYGEN_TMPLShygen at its core, and pack your own templates with it.The templates folder structure maps directly to the command structure:
$ hygen worker new jobrunner
For this command, hygen worker new maps to _templates/worker/new and all files within worker/new are rendered.
Template parameters are given with --flag VALUE, as many as you'd like. In this example we've set a parameter named name to the value jobrunner.
A subcommand is a file inside a your folder structure. So if the structure is this:
_templates/
worker/
new/
index.html.ejs
setup.html.ejs
And you only want setup, you can run:
$ hygen worker new:setup
You can also use the subcommand as a regular expression so, these will do the same:
$ hygen worker new:se
$ hygen worker new:se.*
Here's how a template looks like (in our example, index.ejs.t). Templates bodies are ejs:
---
to: app/workers/<%=name%>.js
---
class <%= h.capitalize(name) %> {
work(){
// your code here!
}
}
The first part of the template is a front matter, idea borrowed from Markdown, this is the metadata part of a hygen template and is part of the reason of why your templates will feel more lightweight and flexible.
All frontmatter metadata are also run through the template engine so feel free to use variables in the frontmatter as you wish.
There's one required metadata variable: to.
to points to where this file will be placed (folders are created as needed).
hygen provides ability to semantic case changing with change-case library, it's simple to use and very easy to understand.
There is a usecase for react based components generation:
---
to: components/<%= name %>/index.jsx
---
import React from 'react'
export const <%= name %> = ({ children }) => (
<div className="<%= h.changeCase.paramCase(name) %>">{children}</div>"
)
With name HelloWorld will be compiled to:
import React from 'react'
export const HelloWorld = ({ children }) => (
<div className="hello-world">{children}</div>"
)
You can see the full list here.
By default templates are 'added' to your project as a new target file, but you can also choose to inject a template into an existing target file.
For this to work, you need to use inject: true with the accompanied inject-specific props.
---
to: package.json
inject: true
after: dependencies
skip_if: react-native-fs
---
"react-native-fs":"*",
This template will add the react-native-fs dependency into a package.json file, but it will not add it twice (see skip_if).
Here are the available mutually-exclusive options for where to inject at:
before | after - a regular expression / text to locate. The inject line will appear before or after the located line.prepend | append - add a line to start or end of file respectively.line_at - add a line at this exact line number.You can guard against double injection:
skip_if - a regular expression / text. If exists injection is skipped.Also you can insert or remove empty line to injection body. That feature very useful if your editor or formatter automatically insert blank line at the end of file on save:
eof_last - if falsy - trim blank line from the end of injection body, if truthy - insert it.hygen is highly embeddable. You should be able to use it by simply listing it
as a dependency and having this kind of workflow in your binary.
const { runner } = require('hygen')
const Logger = require('hygen/dist/logger')
const path = require('path')
const defaultTemplates = path.join(__dirname, 'templates')
runner(process.argv.slice(2), {
templates: defaultTemplates,
cwd: process.cwd(),
logger: new Logger.default(console.log.bind(console)),
createPrompter: () => require('enquirer'),
exec: (action, body) => {
const opts = body && body.length > 0 ? { input: body } : {}
return require('execa').shell(action, opts)
},
debug: !!process.env.DEBUG
})
The Hygen codebase has a functional style where possible. This is because naturally, it feeds parameters and spits out files. Try to keep it that way.
Running hygen locally, rigged to your current codebase (note the additional -- to allow passing flags)
$ yarn hygen -- mailer new foobar
Running tests in watch mode:
$ yarn watch
The easiest way to make an impact is to use the built-in metaverse tests suite, and then add the tests here.
The idea here is to copy templates from any project that use hygen and to test that it works at all times. This keeps tabs on the hygen universe / ecosystem (nicknamed metaverse) and makes sure this test suite breaks before hygen clients break.
Many generators become painfully slow to use as the thing you want to generate grow (because, real life),
This is why hygen takes speed as a first class citizen, and sports a dedicated start-up timing suite:
$ yarn test:require
In addition, thought is given to which dependencies to take in, how their file structure fan out and what kind of disk access (due to require) would hygen ultimately have when we run it. This is recorded with every test run.
Bundling a single file was evaluated (and the infrastructure is still there, using webpack) and wasn't faster than what we have right now.
Fork, implement, add tests, pull request, get my everlasting thanks and a respectable place here :).
To all Contributors - you make this happen, thanks!
Copyright (c) 2018 Dotan Nahum @jondot. See LICENSE for further details.