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  bootstrapping  javascript  productivity  programming  ]
Android development with Docker automation productivity
Getting familiar with Terraform automation productivity
Taking over existing instances with Terraform automation productivity
Terraform automation productivity
Sorting algorithms javascript programming