DEV Community

Cover image for Creating Scaffolds and Generators using Yeoman.
Ricardo Manoel
Ricardo Manoel

Posted on

Creating Scaffolds and Generators using Yeoman.

A quick introduction about Yeoman: according to the official documentation, it is a helper tool to kickstart new projects, prescribing best practices and tools to help you stay productive.
Yeoman doesn't depend on what technology your Scaffold Project is. Each generator is a standalone tool built by Yeoman.
For your information, there are more than 5000 generators with many kinds of technologies and proposes. You can check the generators available here: Discovering Generators.

In this devto article, we will implement a generator to create a dummy NodeJS API and Web App React and talk more about Yeoman generator.

GitHub Repository: https://github.com/ricardoham/generator-scaffold


Installation

Let's start with Yeoman global installation:

npm install -g yo
Enter fullscreen mode Exit fullscreen mode

yo is the Yeoman command line utility allowing the creation of projects utilizing scaffolding templates.


Our project is a Generator that users can choose one of these options: Front-End React, Node API, or a FullStack application via CLI(command-line interface).

Create the project folder:

$ mkdir generator-scaffold && cd generator-scaffold
Enter fullscreen mode Exit fullscreen mode

Run for package.json initial entries.

npm init
Enter fullscreen mode Exit fullscreen mode

And install the lasted version of Yeoman as a dependency.

npm install --save yeoman-generator
Enter fullscreen mode Exit fullscreen mode

Add the name of the generator in the "name" property of package.json like bellow:

{
  "name": "generator-scaffold",
  "version": "0.1.0",
  "description": "scaffold example project",
  "files": [
    "generators"
  ],
  "keywords": [
    "yeoman-generator"
  ],
  "repository": {
    "type": "git",
    "url": "git+https://github.com/ricardoham/generator-scaffold"
  },
  "dependencies": {
    "chalk": "^2.4.2",
    "yeoman-generator": "^3.2.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

By convention, the folder must be named generator-<name_of_genenarator>. This is important because Yeoman will check if this exists on the file system to find available generators.

Use files property as an array, it will be used to set the files and directories of the generator.

Create a sequence of folders:

|- generators
|-- app
|--- templates
|---- api
|---- frontend
Enter fullscreen mode Exit fullscreen mode

Basically, the generators folder will hold all the generators applications(app) and templates is the scaffolding, in this example, we have api and frontend scaffolds.


Coding the generator

Create index.js file in the app folder and then add the main class import yeoman-generator and add our first code lines:

module.exports = class extends Generator {

  constructor(args, opts) {
    super(args, opts);
    this.argument('appname', { type: String, required: false });
  }
};
Enter fullscreen mode Exit fullscreen mode

Using the constructor we can put custom code that will be called first, like in this example, the appname is not a required argument when starting the application.


One of the main goals of creating a generator is the interaction with the user. To do so we can use Prompts. The prompts module is provided by Inquire.js, a third-party library with a collection of common interactive command line user interfaces(CLI). In general, a prompt method is async and returns a promise. It's totally friendly of async-await from ECMAScript spec.

async prompting() {
    this.answers = await this.prompt([{
      type: 'input',
      name: 'name',
      message: 'Your project name',
      default: this.appname, // appname return the default folder name to project
      store: true,
    },
    {
      type: 'list',
      name: 'templateType',
      message: 'Select the template wanted:',
      choices: ['Front-End React', 'Node API builder', 'FullStack Application']
    }]);
  }
Enter fullscreen mode Exit fullscreen mode

During the run process, the methods will run one by one sequentially on the Yeoman loop. To avoid calling a method by mistake there are three approaches:

  1. Use a private method
  2. Use instance methods.
  3. Extend a parent generator.

So in this case let's create custom private methods that won't run on Yeoman loop:

// ...
_writingReactTemplate() {
    this.fs.copy(
      this.templatePath('frontend'),
      this.destinationPath('frontend')
    )
    this.fs.copyTpl(
      this.templatePath('frontend/public/index.html'),
      this.destinationPath('frontend/public/index.html'),
      { title: this.answers.name } // Embedded JavaScript templating.

    )
  }

  _writingApiTemplate() {
    this.fs.copy(
      this.templatePath('api'),
      this.destinationPath('api')
    )
  }
Enter fullscreen mode Exit fullscreen mode

Take a look at these methods: this.fs.copyTpl this.fs.copy
this.destinationPath this.templatePath
Yeoman interacts with the file system using some of these methods, here we set a template path and a destination of the copy folder, with copyTpl We can Embed the name of the project into HTML file template for example in

tag.
{ title: this.answers.name } // Embedded JavaScript templating.
Enter fullscreen mode Exit fullscreen mode

It uses the EJS syntax to do so.

// index.html
<title><%= title %></title>
Enter fullscreen mode Exit fullscreen mode

More information: https://yeoman.io/authoring/file-system.html

And we can use the user inputs storage on this.answers to handle the scaffold the application will create:

// ...
  writing() {
    if (this.answers.templateType === 'Front-End React') {
      this._writingReactTemplate();
    } else if (this.answers.templateType === 'Node API builder') {
      this._writingApiTemplate()
    }
    else {
      this._writingReactTemplate()
      this._writingApiTemplate()
    }
  }
Enter fullscreen mode Exit fullscreen mode

Notice there is a method called: writing() in the snippet above, Yeoman defines a list of priority methods that means when the loop is running some of the special methods could be called, the higher priority it's initializing and the less it's end.

The complete priority methods:

initializing - the method's initialization for example the initial state of the project, initial configs, etc.
prompting - CLI prompt for options to the user.
configuring - To save project configs and save metadata
default - Usable when a method doesn't merge with application priority.
writing - It's responsible to write the specifics files of a generator for example: template, routes, etc.
conflicts - Handler for conflicts(internal use).
install - Where the install methods are called(npm, bower, go get).
end - Last method to call we can put finish messages, cleanup, etc.

The code from index.js:

var Generator = require('yeoman-generator');
var chalk = require('chalk');

module.exports = class extends Generator {

  constructor(args, opts) {
    super(args, opts);
    this.argument('appname', { type: String, required: false });
  }

  // Async Await
  async prompting() {
    this.answers = await this.prompt([{
      type: 'input',
      name: 'name',
      message: 'Your project name',
      default: this.appname, // appname return the default folder name to project
      store: true,
    },
    {
      type: 'list',
      name: 'templateType',
      message: 'Select the template wanted:',
      choices: ['Front-End React', 'Node API builder', 'FullStack Application']
    }]);
  }

  install() {
    this.npmInstall();
  }

  writing() {
    if (this.answers.templateType === 'Front-End React') {
      this._writingReactTemplate();
    } else if (this.answers.templateType === 'Node API builder') {
      this._writingApiTemplate()
    }
    else {
      this._writingReactTemplate()
      this._writingApiTemplate()
    }
  }

  _writingReactTemplate() {
    this.fs.copy(
      this.templatePath('frontend'),
      this.destinationPath('frontend')
    )
    this.fs.copyTpl(
      this.templatePath('frontend/public/index.html'),
      this.destinationPath('frontend/public/index.html'),
      { title: this.answers.name } // Embedded JavaScript templating.

    )
  }

  _writingApiTemplate() {
    this.fs.copy(
      this.templatePath('api'),
      this.destinationPath('api')
    )
  }

  end() {
    this.log(chalk.green('------------'))
    this.log(chalk.magenta('***---***'))
    this.log(chalk.blue('Jobs is Done!'))
    this.log(chalk.green('------------'))
    this.log(chalk.magenta('***---***'))
  }
};
Enter fullscreen mode Exit fullscreen mode

I use chalk lib to colorize the prompt and some methods of Yeoman to illustrate the priorities.


Running the Generator

run npm link in the root folder of this project
navigate to the directory you want to run the generator ex: my_new_project_folder 
run yo scaffold and follow the instructions of CLI

CLI

Troubleshooting

Got any error?
Try to run yo doctor on app root, the yeoman doctor catches errors about what is missing(dependencies, maybe a malfunction method, etc)
You can also use this.log(something) or you can debug your generator following this tutorial: https://yeoman.io/authoring/debugging.html


That's all folks, I hope you enjoy this tutorial and help you to create your own generators.
Thank you, stay safe! 👋

Top comments (6)

Collapse
 
adilgro profile image
Adil grover

One of the main goals of creating a generator is the interaction with the user. To do so we can use Prompts. Where do we add boiler plate code following this? This code :
async prompting() {
this.answers = await this.prompt([{
type: 'input',
name: 'name',
message: 'Your project name',
default: this.appname, // appname return the default folder name to project
store: true,
},
{
type: 'list',
name: 'templateType',
message: 'Select the template wanted:',
choices: ['Front-End React', 'Node API builder', 'FullStack Application']
}]);
}

Collapse
 
coderallan profile image
Allan Simonsen

Wow cool! This will sure come in handy sometime in the future.
Thanks for sharing!

Collapse
 
arvindpdmn profile image
Arvind Padmanabhan

This is a good intro with code samples. If someone is looking for a high-level overview of Yeoman before starting coding, this might help: devopedia.org/yeoman

Collapse
 
adilgro profile image
Adil grover

Trying to copy from a source that does not exist:/Users/adilgrover/Desktop/workspace/generator-scaffold/generators/app/templates/frontend

Collapse
 
aaestrada profile image
aaestrada

i see the npmInstall() now is deprecated. What changes is required now?

Collapse
 
ricardoham profile image
Ricardo Manoel

Thank you guys this really encourages me to write more tutorials!