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
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
Run for package.json initial entries.
npm init
And install the lasted version of Yeoman as a dependency.
npm install --save yeoman-generator
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"
}
}
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
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 });
}
};
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']
}]);
}
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:
- Use a private method
- Use instance methods.
- 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')
)
}
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
{ title: this.answers.name } // Embedded JavaScript templating.
It uses the EJS syntax to do so.
// index.html
<title><%= title %></title>
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()
}
}
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('***---***'))
}
};
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
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)
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']
}]);
}
Wow cool! This will sure come in handy sometime in the future.
Thanks for sharing!
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
Trying to copy from a source that does not exist:/Users/adilgrover/Desktop/workspace/generator-scaffold/generators/app/templates/frontend
i see the npmInstall() now is deprecated. What changes is required now?
Thank you guys this really encourages me to write more tutorials!