DEV Community

loading...
Cover image for Creating Scaffolds and Generators using Yeoman.

Creating Scaffolds and Generators using Yeoman.

ricardoham profile image Ricardo Manoel ・5 min read

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! 👋

Discussion (3)

pic
Editor guide
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
ricardoham profile image
Ricardo Manoel Author

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