Understanding Yeoman

A practical guide to Yeoman: what it is, how to run generators, how to author your own generator, and how to test it.

Understanding Yeoman

What is Yeoman?

~45 minute read

Yeoman is a simple, structured, and powerful code generation tool used to scaffold code of any kind (from a single file to an entire project).

A rich ecosystem of open-source, extensible Yeoman generators (built by the community) is available on the Yeoman site: Yeoman generators.

Find a generator you’re interested in and bootstrap an application quickly—or read on to learn how to build one yourself.

How do I use it?

Prerequisites:

  • Node.js and npm installed (and usable without sudo)1
  • Yeoman installed globally (yo)2

First: what is a generator? A generator is just a script that runs with the intent of creating source code files. It can generate one file or an entire project. By providing boilerplate, you can encode best practices and give new projects a solid foundation.

Yeoman isn’t only for the beginning of a project. Because it runs on Node.js, you can use the full ecosystem to generate code from data: remote APIs, internal services, files on disk, etc.

Run a generator

There are thousands of different Yeoman generators. Once you find one you’re interested in, getting a complete application up and running is often as easy as 1–2–3:

  1. Install a generator with npm (for example): npm install -g generator-angular3
  2. Run it: yo angular
  3. Follow the prompts

Sub-generators are just as easy, and passing arguments is a breeze. For example:

yo angular:controller MainController

Create a generator

Yeoman generators are configuration-based, and writing one is pretty approachable once you learn a few core concepts:

  • How the generator “run loop” works (lifecycle steps)
  • How templates get copied and interpolated
  • How to persist config so reruns are safe
  • How to compose generators and create sub-generators
  • How to test generators

The generator

Start by creating a directory for your generator project. We’ll call it generator-hello-yeoman.

mkdir generator-hello-yeoman
cd generator-hello-yeoman
npm init -y
npm install --save yeoman-generator

By convention, Yeoman uses the app/ directory to contain the base generator. Create your entrypoint at app/index.js.

Here’s a simple “Hello World” generator:

'use strict';
 
const Generator = require('yeoman-generator');
 
module.exports = class extends Generator {
  writing() {
    this.log('Hello World');
  }
};

To make the generator runnable locally, link it:

npm link

Now you can run it (the command uses the name after generator-):

yo hello-yeoman

Older Yeoman code often used generator.Base.extend(...). Modern Yeoman commonly uses ES6 classes as shown above—the concepts are the same.

Config conventions (the run loop)

Yeoman has a well-defined execution order. The most common lifecycle steps are:

  • initializing
  • prompting
  • configuring
  • default
  • writing
  • conflicts
  • install
  • end

Each step can be a function or an object of functions. The main point: Yeoman gives you a structured place to do each kind of work.

Code templates

Templates usually live in:

  • app/templates/

Yeoman has two useful “contexts”:

  • Template context: where templates are read from (this.templatePath(...))
  • Destination context: where files are written (this.destinationPath(...))

A common convention is to prefix template filenames with _ (and then write them without it), and to handle dotfiles specially (e.g. _gitignore.gitignore).

Copying files

Copy a file:

this.fs.copy(this.templatePath('index.html'), this.destinationPath('src/index.html'));

Copy a directory:

this.fs.copy(this.templatePath('src'), this.destinationPath('src'));

Templating syntax

Use <%= variableName %> inside template files to interpolate values.

To copy a template file and inject variables:

this.fs.copyTpl(
  this.templatePath('index.html'),
  this.destinationPath('src/index.html'),
  { variableName: 'Foo' },
);

Create JSON files programmatically

this.fs.writeJSON(this.destinationPath('config.json'), { hello: 'world' });

Install dependencies automatically

Inside the install step, you can run package managers. For example:

install() {
  this.npmInstall();
}

Because installs can be expensive, Yeoman supports --skip-install to skip that step.

Arguments

Arguments allow you to pass values to the generator (often for naming):

this.argument('name', { type: String, required: true });

Use it like this.options.name (or assign it to a property if you prefer).

Options (command-line switches)

Options are similar to arguments, but are often booleans:

this.option('includeUtils', {
  desc: 'Include additional utility helpers',
  type: Boolean,
  default: false,
});

Prompts

Prompts let you ask questions and generate customized code. Under the hood, Yeoman uses inquirer.

Example prompt (modern async style):

async prompting() {
  const answers = await this.prompt([
    {
      type: 'input',
      name: 'appName',
      message: 'What is the application name?',
      default: 'app',
    },
  ]);
 
  this.log(answers);
}

Persist config

If you add store: true to prompts, Yeoman will remember the last answers. You can also use the storage API directly:

this.config.set('appName', 'helloWorld');
const appName = this.config.get('appName');

Generator composition

Generator composition is a great feature: you can reuse generators as building blocks.

Docs: Yeoman composability

Conceptually, you call this.composeWith(...) to run another generator as part of yours.

Sub-generators

Sub-generators are just generators living in a different folder than app/. You run them by appending :name:

yo angular:controller foo

How to test a Yeoman generator

Mocha is a good fit for generator tests. A straightforward setup today is:

npm install --save-dev mocha yeoman-test yeoman-assert

Then create a test/ directory and add a test file (example: test/app.test.js):

'use strict';
 
const path = require('path');
const helpers = require('yeoman-test');
const assert = require('yeoman-assert');
 
describe('generator-hello-yeoman', function () {
  it('creates expected files', function () {
    return helpers
      .run(path.join(__dirname, '../app'))
      .withOptions({ skipInstall: true })
      .then(() => {
        assert.file(['package.json']);
      });
  });
});

Run tests:

npx mocha

Appendix

Credits

This post was heavily inspired by Steve Michelotti’s Yeoman course on Pluralsight. If anything here feels fuzzy, that course is a great walkthrough.

Footnotes

  1. To use npm effectively, it helps to set things up so global installs don’t require sudo. One guide: How to use npm global without sudo on OSX. Also see Stack Overflow and sindresorhus guides.

  2. Install Yeoman globally with npm install -g yo.

  3. Yeoman generators are node packages whose name starts with generator-. For example, installing generator-angular gives you yo angular.