Every time I start a new project I go to old projects to review and copy my build steps and linting rules among other things. A good developer would have automated the creation of a new project instead of going back every time. I want to redeem myself so I’m taking a look a Yeoman.
Yeoman is a tool for scaffolding web apps. It basically allows you to create custom reusable app skeletons called generators.
Install
1
npm install -g yo
Using a generator
There are many open source generators out there that you can use out of the box. You can try the Angular generator with these commands:
1
2
3
4
mkdir ~/angular-app
cd ~/angular-app
npm install -g generator-angular
yo angular
Then you will be asked a few questions about your project and a skeleton will be created for you.
Creating a generator
There are many generators already available but you might(like me) have a very specific way you like to do things, in which case you will need to create your own generator.
Lets start our generator project:
1
2
3
4
mkdir ~/generator-example-adrian
cd ~/generator-example-adrian
npm install -g generator-generator
yo generator
We just ran a generator to create our generator skeleton. Lets test it:
1
2
3
4
npm link
mkdir ~/some-project
cd ~/some-project
yo example-adrian
Since the generator we are working on is not yet available as a global npm module we had to use npm link to make it available for testing. Then we created a folder and ran our generator in that folder.
The generator we are going to create for this example will:
- Create a README.md
- Create .gitignore
- Create Gruntfile.js
- Add ESLint support
Lest start by creating our templates. There are some templates already in app/templates. Delete all but _package.json
. We are also going to create the following templates and save them under that folder:
readme.md
1
This is just an example
gitignore
1
/node_modules/
gruntfile.js
1
2
3
4
5
6
7
8
9
10
11
'use strict';
module.exports = function(grunt) {
var config = {
<%= eslint %>
};
grunt.initConfig(config);
require('load-grunt-tasks')(grunt);
};
eslintrc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"env": {
"node": true
},
"rules": {
"quotes": [1, "single"],
"brace-style": [2, "1tbs"],
"no-spaced-func": [2, true],
"valid-jsdoc": [0, true],
"camelcase": [2, true],
"space-in-brackets": [2, "never"],
"space-infix-ops": [2, true],
"space-after-keywords": [2, "always"]
}
}
eslintGruntConfig
1
2
3
4
5
eslint: {
target: [
'Gruntfile.js'
]
}
Now we need to modify our app/index.js file a little:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
'use strict';
var yeoman = require('yeoman-generator');
var chalk = require('chalk');
var yosay = require('yosay');
module.exports = yeoman.generators.Base.extend({
// Say hi to the user
greet: function() {
this.log(yosay('Hello user'));
},
// Asks the user if they want to enable ESLint
// and saves their answer for later use
prompting: function() {
var done = this.async();
var prompts = [{
type: 'confirm',
name: 'eslint',
message: 'Would you like to enable eslint?',
default: true
}];
this.prompt(prompts, function(props) {
this.eslint = props.eslint;
done();
}.bind(this));
},
// Generate files
writing: {
// Write the files that will be written
// regardless of how the user responded
// to our prompt
common: function() {
// README.md
this.fs.copy(
this.templatePath('readme.md'),
this.destinationPath('README.md')
);
// .gitignore
this.fs.copy(
this.templatePath('gitignore'),
this.destinationPath('.gitignore')
);
// package.json
this.fs.copy(
this.templatePath('_package.json'),
this.destinationPath('package.json')
);
},
// Gruntfile.js is rendered differently
// if ESLint is enabled or not
gruntfile: function() {
var options = {
eslint: ''
};
if (this.eslint) {
options.eslint = this.fs.read(this.templatePath('eslintGruntConfig'));
}
this.fs.copyTpl(
this.templatePath('gruntfile.js'),
this.destinationPath('Gruntfile.js'),
options
);
},
// Write the .eslintrc file if user chose
// to use ESLint
eslint: function() {
if (this.eslint) {
this.fs.copy(
this.templatePath('eslintrc'),
this.destinationPath('.eslintrc')
);
}
}
},
install: function() {
// Only install grunt-eslint if necessary
if (this.eslint) {
this.npmInstall(['grunt-eslint'], {saveDev: true});
}
this.npmInstall(['grunt', 'load-grunt-tasks'], {saveDev: true});
}
});
You can see in my example that I use a template to add the ESLint configuration to the Gruntfile. This is a bad practice that I only used to make this example simpler. There are good practices for modifying gruntfiles available in the Yeoman documentation.
From now on, starting new projects will be a lot more efficient.
automation
javascript
productivity
programming
]