Writing a Node.js Project Generator with Yeoman
While writing my post on starting a Node.js application, I was already looking around for a solution that creates projects from a boilerplate.
Yeoman is a Node.js command line utility that offers several generators. It even offers a generator to create Yeoman generators.
A fine image from Yeoman’s generator-generator Github page
To begin with, install Yeoman and the generator-generator, which generates generator projects. Then create your project directory, which should be prefixed with the word generator-
.
Then invoke the generator-generator with the yo generator
command.
npm install -g yo generator-generator
mkdir -p generator-node-express-application
cd generator-node-express-application
yo generator
Yeoman Documentation on writing a generator
The generator has this directory structure:
project_dir
├── generators/
│ └── app/
│ ├── templates/
│ └── index.js
└── package.json
I’m going to modify the generator to simply copy files from the templates directory into the target project directory. This way, I can copy all
of the source code for my Express application into the templates
directory.
It is possible to use EJS templates to dynamically render text files, but I’m not going to go down that rabbit hole. Similarly, I’m not going to dynamically create Javascript code for this project. Not yet, anyway…
When copying the package.json, I am going to dynamically modify it, asking the same questions that npm init
would ask. Since all the dependencies are already listed in package.json, the generators final internal npm install
will install all the dependencies.
generators/app/index.js
'use strict';
const Generator = require('yeoman-generator');
const Promise = require('bluebird');
const _ = require('lodash');
const glob = Promise.promisify(require('glob'));
module.exports = class extends Generator {
prompting() {
this.log('Node.js Express Application Generator');
const prompts = [
{
type: 'input',
name: 'name',
message: 'package name:',
default: this.appname
},
{
type: 'input',
name: 'version',
message: 'version:',
default: '1.0.0'
},
{
type: 'input',
name: 'description',
message: 'description:',
},
{
type: 'input',
name: 'gitRepository',
message: 'git repository:',
},
{
type: 'input',
name: 'keywords',
message: 'keywords:',
},
{
type: 'input',
name: 'author',
message: 'author:',
},
{
type: 'input',
name: 'license',
message: 'license:',
default: 'MIT'
},
];
return this.prompt(prompts).then(props => {
this.props = props;
});
}
writing() {
const generator = this;
const fs = generator.fs;
const props = generator.props;
return glob('**/*.*', {
cwd: generator.templatePath(),
dot: true,
ignore: ['package-lock.json']
})
.each(function (file) {
let templatePath = generator.templatePath(file);
let destinationPath = generator.destinationPath(file);
if (file === 'package.json') {
const packageJson = fs.readJSON(templatePath);
_.assign(packageJson, {
"name": `${props.name}`,
"version": `${props.version}`,
"description": `${props.description}`,
"author": `${props.author}`,
"license": `${props.license}`
});
if (!_.isEmpty(props.keywords)) {
packageJson.keywords = props.keywords.split(/s+/);
}
if (!_.isEmpty(props.gitRepository)) {
packageJson.git = {type: git, url: props.gitRepository};
}
fs.writeJSON(packageJson, destinationPath);
} else {
fs.copyTpl(templatePath, destinationPath);
}
});
}
install() {
this.installDependencies({bower: false});
}
};
Testing the Generator
To test the generator, npm needs to know about it. At the root of your generator project:
npm link
You can then test the generator by running the following command in an empty directory.
yo node-express-app
Publishing to NPM
I am going to use NPM scoped packages so I can publish my generator without fear of name collisions.
Inside my generator package.json:
{
...
"name": "@lohsy84/generator-node-express-application",
...
}
To publish it to npm, I run the following command.
npm publish
Finally, I can invoke the published generator with the following command:
npm install @lohsy84/generator-node-express-application
yo @lohsy84/generator-node-express-application