hygen vs plop vs sao vs yeoman-generator
Architecting Scalable Code Generators for Frontend Teams
hygenplopsaoyeoman-generatorSimilar Packages:

Architecting Scalable Code Generators for Frontend Teams

hygen, plop, sao, and yeoman-generator are tools designed to automate file creation and project scaffolding, helping teams enforce consistency and reduce boilerplate. hygen focuses on speed and simplicity using template files with embedded prompts, ideal for generating small, repetitive components. plop offers a JavaScript-centric configuration approach, allowing dynamic logic within the generation process itself. sao (Scaffolding Old) is a lightweight, modern alternative built on EJS templates, often used for initializing entire projects quickly. yeoman-generator is the engine behind the famous Yeoman ecosystem, providing a robust, class-based architecture for complex, multi-step scaffolding workflows that require deep integration with package managers and file systems.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
hygen05,934137 kB102-MIT
plop07,675164 kB918 months agoMIT
sao01,045-676 years agoMIT
yeoman-generator01,265157 kB39 days agoBSD-2-Clause

Architecting Scalable Code Generators: Hygen vs Plop vs Sao vs Yeoman

When scaling a frontend application, consistency is key. Manually creating files leads to typos, inconsistent folder structures, and wasted time. This is where code generators come in. They automate the boring stuff so developers can focus on logic. We have four strong contenders: hygen, plop, sao, and yeoman-generator. While they all create files, they solve the problem in very different ways. Let's dig into how they work under the hood.

⚑ Execution Model: File-Based vs Code-Based

The biggest difference lies in where you define your logic. Do you write templates in separate files, or do you write JavaScript code to generate files?

hygen keeps everything in the file system. You create a template file with special markers for prompts. The tool reads these files and runs them. There is no separate "generator code" to maintain.

# hygen: Template file at _templates/component/new.ejs.t
---
to: src/components/<%= name %>/<%= name %>.js
---
import React from 'react';

export default function <%= name %>() {
  return <div><%= name %> Component</div>;
}

plop moves the logic into a JavaScript file (plopfile.js). You define actions and prompts using code. This gives you more power to change behavior based on conditions.

// plop: plopfile.js
module.exports = function (plop) {
  plop.setGenerator('component', {
    prompts: [{ type: 'input', name: 'name' }],
    actions: [{
      type: 'add',
      path: 'src/components/{{name}}/{{name}}.js',
      templateFile: 'templates/component.hbs'
    }]
  });
};

sao uses a simple sao.js or index.js file but focuses on being extremely lightweight. It often relies on EJS templates similar to Hygen but wraps them in a minimal API designed for quick project initialization rather than daily component generation.

// sao: index.js (generator entry)
module.exports = {
  prompts: [{
    type: 'string',
    name: 'name',
    message: 'Project name'
  }],
  templateData() {
    return { name: this.prompts.name };
  }
};

yeoman-generator uses a class-based structure. You extend a base Generator class and define methods like writing() and installing(). This is heavy but allows for complex lifecycle management.

// yeoman-generator: generators/app/index.js
const Generator = require('yeoman-generator');

module.exports = class extends Generator {
  writing() {
    this.fs.copyTpl(
      this.templatePath('component.js'),
      this.destinationPath(`src/components/${this.options.name}.js`),
      { name: this.options.name }
    );
  }
};

πŸ“ Template Syntax: EJS vs Handlebars

How you write the actual content of the generated files matters for readability and power.

hygen uses EJS (Embedded JavaScript). Since it's just JavaScript inside your templates, you can use loops, conditionals, and helper functions directly in the template file.

<!-- hygen template -->
<% if (type === 'functional') { %>
const <%= name %> = () => <div>Hi</div>;
<% } else { %>
class <%= name %> extends React.Component { ... }
<% } %>

plop defaults to Handlebars ({{ }}). It is safer and less prone to accidental code execution, but sometimes feels limited if you need complex logic inside the template.

<!-- plop template (component.hbs) -->
{{#ifEquals type "functional"}}
const {{name}} = () => <div>Hi</div>;
{{else}}
class {{name}} extends React.Component { ... }
{{/ifEquals}}

sao also leans heavily on EJS, offering the same flexibility as Hygen for embedding logic directly into the output files.

<!-- sao template -->
<% features.forEach(feature => { %>
import { <%= feature %> } from 'lib';
<% }) %>

yeoman-generator supports both EJS and Handlebars via its internal file system engine (mem-fs-editor). It gives you the choice, but requires you to explicitly choose the rendering method in your code.

// yeoman-generator: Using EJS explicitly
this.fs.copyTpl(
  this.templatePath('index.ejs'),
  this.destinationPath('index.html'),
  { title: 'My App' } // Context passed to EJS
);

πŸ”„ Workflow Integration: Daily Use vs One-Off Setup

Not all generators are meant for the same job. Some are for setting up a project once; others are for use every day.

hygen is built for daily use. You run hygen component new dozens of times a week. It is fast, has no build step, and lives right next to your source code. It feels like part of your app, not a separate tool.

# Typical Hygen usage
$ hygen component new Button
✨  Done in 0.5s

plop is also great for daily use but requires a Node environment to run the plopfile.js. It is slightly heavier to start but offers more interactive prompts out of the box.

# Typical Plop usage
$ npx plop component
? What is the component name? Button
✨  File created

sao is typically used for "one-off" project scaffolding. You run it once to create a new repo, then you might never use it again for that specific project. It is optimized for initializing entire structures.

# Typical Sao usage
$ npx sao my-project-template new-project
✨  Project initialized

yeoman-generator is the heavyweight champion for complex setups. It handles package installation (npm install), git initialization, and complex directory trees. It is overkill for a single component but perfect for generating a full microservice or library.

# Typical Yeoman usage
$ yo my-full-stack-app
? Choose database: PostgreSQL
πŸš€  Installing dependencies...
✨  App ready

πŸ› οΈ Extensibility and Custom Logic

What happens when you need to do more than just copy a file? Maybe you need to append to an existing file or run a shell command.

hygen allows you to inject code into existing files using specific templates. It is simple but effective for adding exports to an index.js.

# hygen: Injecting into index.js
$ hygen component inject Button
# Adds: export { default as Button } from './Button';

plop has a dedicated append action. You can precisely target a file and add text to the end or beginning, controlled entirely by your JS logic.

// plop: Append action
actions: [{
  type: 'append',
  path: 'src/components/index.js',
  pattern: /(?=<\/exports>)/,
  template: "export { default as {{name}} } from './{{name}}';"
}]

sao keeps it minimal. While you can run shell commands in the generator script, it doesn't have built-in "inject" helpers as rich as Plop or Hygen. You usually write the full file content.

// sao: Manual file writing
this.fs.write(
  'src/index.js',
  existingContent + '\nexport { default as Button } from "./Button";'
);

yeoman-generator shines here. It has a robust API for modifying JSON files, appending to files, and running shell commands asynchronously with proper error handling. It treats file manipulation as a transaction.

// yeoman-generator: Modifying package.json
this.packageJson.merge({
  scripts: {
    test: 'jest'
  }
});
// Automatically handles reading/writing the JSON safely

🌐 Real-World Scenarios

Scenario 1: The Daily Component Factory

Your team creates 5-10 new React components every day. You need speed and zero friction.

  • βœ… Best choice: hygen
  • Why? It's the fastest to run. Templates live in the repo. No complex setup.
# Developer experience
$ hygen component new Navbar
# Instantly creates files and injects exports

Scenario 2: The Dynamic Micro-Service Generator

You need to generate a backend service where the file structure changes based on whether the user selects SQL or NoSQL.

  • βœ… Best choice: plop
  • Why? The JavaScript config allows if/else logic to decide which files to create dynamically.
// plopfile.js logic
if (dbType === 'sql') {
  actions.push({ type: 'add', path: 'db/schema.sql' });
} else {
  actions.push({ type: 'add', path: 'db/models.js' });
}

Scenario 3: The Quick Starter Kit

You want to publish a template on GitHub that users can clone and initialize in one command.

  • βœ… Best choice: sao
  • Why? It's lightweight, easy to distribute via npx, and perfect for one-time setup.
# User runs this once
$ npx sao github:user/my-template my-app

Scenario 4: The Enterprise Library Scaffold

You are building a tool that generates a full npm package, sets up CI/CD, configures TypeScript strictly, and publishes a initial commit.

  • βœ… Best choice: yeoman-generator
  • Why? You need the lifecycle hooks (install, end) to run npm install and git init reliably.
// Yeoman lifecycle
install() {
  this.npmInstall();
}
end() {
  this.spawnCommand('git', ['init']);
}

πŸ“Š Summary Table

Featurehygenplopsaoyeoman-generator
Config StyleFile-based (EJS)Code-based (JS)Code-based (JS)Class-based (JS)
Template EngineEJSHandlebarsEJSEJS / Handlebars
Primary UseDaily ComponentsDynamic GeneratorsProject StartersComplex Scaffolds
Speed⚑ Very FastπŸƒ FastπŸƒ Fast🐒 Moderate
Learning CurveLowMediumLowHigh
Dependency MgmtManual / SimpleManualManualBuilt-in (npmInstall)

πŸ’‘ Final Recommendation

Think about frequency and complexity.

  • High Frequency, Low Complexity? (e.g., "I make components all day") β†’ Go with hygen. It stays out of your way.
  • Medium Frequency, High Logic? (e.g., "The files change based on user choices") β†’ Go with plop. The JS config handles the brain work.
  • One-Off Setup? (e.g., "Start a new project") β†’ Go with sao. It's light and easy to share.
  • Heavy Duty Automation? (e.g., "Setup a whole repo with deps and git") β†’ Go with yeoman-generator. It handles the heavy lifting of project initialization.

Choosing the right tool prevents your scaffolding from becoming a burden. Keep it simple when you can, and reach for power only when you need it.

How to Choose: hygen vs plop vs sao vs yeoman-generator

  • hygen:

    Choose hygen if your team needs a fast, zero-config tool for generating components, hooks, or screens within an existing project. It excels when you want to keep templates as simple files in your repository without writing complex JavaScript logic for the generator itself. It is the best fit for teams that prioritize developer speed and want to avoid maintaining a separate build step for their scaffolding tool.

  • plop:

    Choose plop if you prefer defining your generation logic entirely in JavaScript, giving you full programmatic control over prompts and file transformations. It is ideal for scenarios where the file structure depends on dynamic user input that requires conditional logic not easily expressed in simple template syntax. Use it when you need a highly customizable CLI experience that integrates tightly with your existing Node.js build tools.

  • sao:

README for hygen

hygen logo

build status npm version

hygen is the simple, fast, and scalable code generator that lives in your project.

Features

  • βœ… Build ad-hoc generators quickly and full on project scaffolds.
  • βœ… Local generators per project (and global, if you must)
  • βœ… Built-in scaffolds to quickly create generators
  • βœ… Full logic templates and rendering
  • βœ… Prompts and flows for taking in arguments
  • βœ… Automatic CLI arguments
  • βœ… Adding new files
  • βœ… Injecting into existing files
  • βœ… Running shell commands
  • βœ… Super fast, constantly optimized for speed

New in hygen v4.0.0: a positional NAME parameter. Now you can use $ hygen component new MyComponent instead of $ hygen component new --name MyComponent.

Used By

Wix Β  Β  Airbnb Β  Β  Mercedes Benz AG Β  Β  Open Data Institute Β  Β  Ableneo Β  City of Amsterdam Β  Β  Accenture Β  Birdie Β  Β  Kind Β  Β  Ackee Β  Aerian Studios Β Food and Agriculture Organization of the UN Cape Cod Commision Β  Tweak Things Β  Β  Crema Β  Β  Cureon Β  Β  Astrocoders Β  Β  Vega/IDL Β  Sporty Spots Β  Β Thrashplay Β Β 8base Β Β  Instamotionh Biotope Frontend Labs Swydo Gridsome Rosem LaboratorySheffield Hallam University Hackoregon Chilly Design

Scale Leap Chat Logs Stelace Echobind.

Quick Start

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

Use a template repo

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

What's Next?

Go to the documentation to get to know the rest of Hygen and generators.

If you're in a hurry:

  • To learn how to edit generator templates, look here
  • To see how to use generators look here
  • Take a look at the ecosystem and tooling built around Hygen.

A Different Kind of a Generator

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.

Scratch Your Own Itch

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.

Template Locality

hygen picks up a local _templates folder at any folder level of your project you're working from.

This is important because:

  • Templates are project-local. A git clone of the project fetches all generators as well.
  • Different generators can be tucked in different parts of the project, making it contextual.
  • Template locality is scalable; different teams can maintain different generators.
  • When you change your code, you can make changes in the template and pack in the same commit, to be reviewed and merged in the same PR (as opposed to installing different "plugins" or different templates from out-of-repo places).

And yet, if you don't like project-local templates:

  • You can have a global _templates folder (maybe a central git repo you maintain?) by populating an environment variable HYGEN_TMPLS
  • You can build a custom generator of your own with hygen at its core, and pack your own templates with it.

Folder Structure is Command Structure

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.

Subcommands

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

Frontmatter for Decluttering

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

Case changing

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.

Addition, Injection, and More

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.

Build Your Own

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

Development

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

Metaverse Testing

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.

Internal Testing

Start Up Speed Testing

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.

Contributing

Fork, implement, add tests, pull request, get my everlasting thanks and a respectable place here :).

Thanks:

To all Contributors - you make this happen, thanks!

Copyright

Copyright (c) 2018 Dotan Nahum @jondot. See LICENSE for further details.