Create Your Own Project, Pattern, Component And Files Generator: Yeoman Vs Plop
Yeoman
Yeoman is a scaffolding tool that helps you kickstart your new projects by providing a generator ecosystem that can be launched with the command yo
.
It is most commonly used to generate entire projects, but can also generate any kind of files.
Installation
- Node.js (with npm included) is required. You can download the latest version from the following link. (If you encounter any difficulties while using the
yo
command with Windows 10, install the version 8 of node instead from the following link) - From the command line, run
npm install -g yo
- Run
npm install -g generator-generator
Getting started
Run the yo generator
command at the folder where you want to generate the generator project. You will be prompted to input the generator name and other details.
The directory tree of the generator project will look like this:
The generators\app\index.js
file is the main entry of the generator.
By default, Bower
is assumed to be installed and to be used to manage dependencies. To disable this behavior, you can set the arguments of the installDependencies
method call of the generator's index.js
to the following:
install() {
this.installDependencies({ bower: false });
}
To be able to launch this generator from any working directory of your computer, run npm link
from the root of the generator project
(You can also publish this module to a npm registry so that it can be used by other users)
You can now test your generator from any directory using the command yo your-generator-name
(without the generator-
prefix):
You will have a dummyfile.txt
file generated in your current working directory.
Sub-generators
You can implement in the same generator project additional generators that can be launched with the yo your-generator-name:sub-generator-name
command.
They can be generated by running yo generator:subgenerator <name>
at the root folder of the generator project
A dummyfile.txt
file will be generated when running the yo your-generator-name:sub-generator-name
command:
Basic generator implementation
You can remove the welcome banner by deleting the following code from the prompting
method
// Have Yeoman greet the user.
this.log(
yosay(
`Welcome to the wonderful ${chalk.red(
"generator-your-generator-name"
)} generator!`
)
);
To prompt the user for the project's name, you can replace the prompts array inside the prompting
method with the following value:
const prompts = [
{
name: 'name',
message: 'Your project name',
default: 'generated-app'
}
];
By default, yeoman generates the files in the current working directory. If you want to generate a folder instead where the template files will be copied:
- Install the
mkdirp
dependency withnpm i -S mkdirp
- In the generator's
index.js
, add the following require call:const mkdirp = require('mkdirp');
- Implement a method before the
writing
method with the following code:default() { mkdirp(this.props.name); this.destinationRoot(this.destinationPath(this.props.name)); }
You can now replace the generator's templates
folder with the files you want to generate.
Yeoman doesn't directly modify the filesystem, but does all the file manipulation in memory and only write the resulting file tree at the end.
this.fs
is a provided in-memory filesystem edition helper based on MemFsEditor that is used to copy files and folders to the new destination. You can check all the available methods in this page.
this.fs.copy can be called to copy files and folders. The templatePath and destinationPath methods are used to join a path to the template source root and destination root respectively.
The template copying logic can be implemented inside the writing
method:
writing() {
this.fs.copy(this.templatePath('*'), this.destinationPath());
this.fs.copy(this.templatePath('.*'), this.destinationPath());
this.fs.copy(this.templatePath('src'), this.destinationPath('src'));
this.fs.copy(this.templatePath('server'), this.destinationPath('server'));
this.fs.copy(this.templatePath('e2e'), this.destinationPath('e2e'));
}
To insert variables inside files, the this.fs.copyTpl method can be used. It supports the ejs templating system.
In the current example, the project name can be inserted inside a template file with: <%= name %>
.
// package.json
{
"name": "<%= name %>",
"version": "1.0.0"
// ...
}
To copy a package.json
template file that includes the name
variable:
writing() {
// ...
this.fs.copyTpl(
this.templatePath('package.json'),
this.destinationPath('package.json'),
this.props // template variables
);
}
Check that the files are copied and that the variables are well replaced after executing the command yo your-generator-name
This is the final index.js
code of this basic generator implementation:
"use strict";
const Generator = require("yeoman-generator");
const mkdirp = require("mkdirp");
module.exports = class extends Generator {
prompting() {
const prompts = [
{
name: "name",
message: "Your project name",
default: "generated-app"
}
];
return this.prompt(prompts).then(props => {
// To access props later use this.props.someAnswer;
this.props = props;
});
}
default() {
mkdirp(this.props.name);
this.destinationRoot(this.destinationPath(this.props.name));
}
writing() {
this.fs.copy(this.templatePath('*'), this.destinationPath());
this.fs.copy(this.templatePath('.*'), this.destinationPath());
this.fs.copy(this.templatePath('src'), this.destinationPath('src'));
this.fs.copy(this.templatePath('server'), this.destinationPath('server'));
this.fs.copy(this.templatePath('e2e'), this.destinationPath('e2e'));
this.fs.copyTpl(
this.templatePath('package.json'),
this.destinationPath('package.json'),
this.props
);
}
install() {
this.installDependencies({ bower: false });
}
};
References and resources
- https://yeoman.io/authoring/
- https://yeoman.github.io/generator/Generator.html
- Get inspired by existing generators to write your own such as:
- More generator projects can be found here: https://yeoman.io/generators/
Plop.js
Plop.js is described as a "micro-generator framework". It's better suited to generate smaller parts of existing projects or any kind of text files in a consistent way. It provides a generator ecosystem based on Inquirer.js prompts and Handlebar.js templates.
Contrary to Yeoman, it can't be used to generate entire projects. In return, it has more adapted features to generate or modify files inside existing projects.
Installation
- Node.js (with npm included) is required. You can download the latest version from the following link.
- From the command line, run
npm install -g plop
- (Optional) If you have a Node.js project, plop can be saved as a dev dependency by running the following command in the root directory of the project
npm install --save-dev plop
Getting started
Create a plopfile.js
file at the root directory of the project where you want to generate files with the following content:
module.exports = function (plop) {
plop.setGenerator('my-generator', {
description: 'My first plop generator',
prompts: [],
actions: [{
type: 'add',
path: 'src/main.js',
template: 'console.log("Hello, World!");'
}]
});
};
Then run the plop my-generator
command from the root directory (or subdirectory) of the project where you want to generate files.
Example with empty project directory containing just the plopfile.js
file:
The src/main.js
file is generated with the console.log("Hello, World!");
content:
Basic generator implementation
In this example, we will be generating controller classes for a Java Spring Boot application (the code was initialized using https://start.spring.io/ with the Web dependency)
Add a plopfile.js
file to the root of the application with the following content:
module.exports = function (plop) {
plop.setGenerator('controller', {
description: 'Spring Boot Rest Controller',
prompts: [{
type: 'input',
name: 'name',
message: 'Controller name'
}],
actions: [{
type: 'add',
path: 'src/main/java/com/example/moviesapp/controller/{{pascalCase name}}Controller.java',
templateFile: 'plop-templates/controller.hbs'
}]
});
};
The prompts
array holds all the variable definition objects that will be requested as user input using Inquirer.js or provided as command line arguments. The variable definition object format is described here.
The actions
array holds the actions that the generator will execute to add and modify files to your project. The built-in actions and their formats are detailed here.
All the variables collected from the prompts are made available by Plop.js to be used in the template files (file name & content). The syntax of these templates is based on Handlebar.js.
Let's create a plop-templates
folder at the root of the project to hold the templates that will be used by the plop generator, and a controller.hbs
file inside it with the following content:
package com.example.moviesapp.controller;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
@RestController
public class {{pascalCase name}}Controller {
@RequestMapping("/{{dashCase name}}")
public String index() {
return "{{pascalCase name}}Controller works!";
}
}
Notice that pascalCase and dashCase helper functions were applied to the variable (inside this template & in the plopfile.js
file) to change its case.
More built-in helpers can be found here.
To generate a new controller in the project using user input, run the plop controller
command:
A new controller can also be generated without user input by running the command plop controller <controller-name>
:
The generated controller file:
Share generators
The previous example can be refactored to make the controller generator available to any project.
If not already done, set the NODE_PATH
environment variable to your global node modules location. Default path examples:
- Linux:
/usr/lib/node_modules
- Windows:
C:\Users\USER_NAME\AppData\Roaming\npm\node_modules
In your workspace, create a directory that will hold all your generators implementation.
Create a package.json
file in this directory with the following contents:
{
"name": "plop-pack-generators",
"version": "1.0.0",
"main": "plopfile.js"
}
Then move the plopfile.js
file and plop-templates
folder from the previous example to this plop generators project directory.
To be able to load this generator from any working directory of your computer, run npm link
from the root of the generator project
(You can also publish this module to a npm registry so that it can be used by other users)
To correctly resolve the templates path, and customize the generator with the current project's specificities (app's root package for example), change the plopfile.js
of the plop generators project with the following content:
const path = require('path');
const templatesPath = path.resolve(__dirname, 'plop-templates')
module.exports = function (plop, config) {
const package = config && config.package
const packagePath = package ? (package.replace(/\./g, "/") + "/") : ""
plop.setGenerator('controller', {
description: 'Spring Boot Rest Controller',
prompts: [{
type: 'input',
name: 'name',
message: 'Controller name'
}],
actions: [{
type: 'add',
path: `src/main/java/${packagePath}controller/{{pascalCase name}}Controller.java`,
templateFile: `${templatesPath}/controller.hbs`,
data: config
}]
});
};
The following plopfile.js
file must be be created in the root directory of the projects where you want to use the generators:
module.exports = function (plop) {
require('plop-pack-generators')(plop, {
package: "your.app.package"
});
};
References and resources
Soufiane Sakhi is an AWS Certified Solutions Architect – Associate and a professional full stack developer.