Creating a generator

  • page
generator  

Generators add additional functionality to DoneJS applications. In this guide, we’ll create a generator that adds JSHint and a .editorconfig file to a DoneJS application.

If you have used donejs add nw or donejs add cordova to create a desktop or mobile version of your application you already used a DoneJS generator. Generators are npm modules that provide a Yeoman generator that adds functionality to your application.

In this guide you will create your own version of donejs-jshint, a DoneJS generator that adds:

  • JSHint, a JavaScript code quality tool
  • A .editorconfig file which helps text editors and IDEs to define and maintain a consistent coding style.

It will also update the npm test script to run JSHint with our tests. You can find the code in the donejs-jshint repository. We can run the generator with:

$ donejs add jshint

Currently it will only ask if we want to use spaces or tabs and to overwrite the package.json to add the npm scripts for JSHint:

DoneJS generator

Note: Since donejs-jshint already exists we will use donejs-<username>-jshint with <username> being your GitHub username for the remainder of this article. Once published it can then be used as donejs add <username>-jshint.

Setting up

Creating the project on GitHub

We will use GitHub to host the code for the project which makes it easy for others to contribute and to automatically run the tests in continuous integration which we will enable later.

If you don't have an account yet, go to GitHub to sign up and follow the help on how to set it up for the command-line git. Once completed, create a new repository from your dashboard.

Calling the repository donejs-<username>-jshint and initializing it empty (without any of the default files) looks like this:

generator-repository

After creating the repository, clone it into a new folder:

$ git clone git@github.com:<username>/donejs-<username>-jshint.git
$ cd donejs-<username>-jshint

Initializing the project

To initialize a new generator you will need DoneJS version 0.9.0+ installed globally. To check your DoneJS version run

$ donejs --version

To install DoneJS or to get the latest version run:

$ npm install donejs -g    

In the donejs-<username>-jshint folder we can now initialize a new generator, very similar to a new DoneJS application, like this:

$ donejs add generator

The generator will ask several question that should be answered as follows:

  • For the project name you can just confirm the default by pressing enter
  • For the GitHub username or organization enter the GitHub username where the repository has been created
  • All other fields can also be answered with the default or the information you would like to use

Once all done, the final prompt looks similar to this:

generator-init

Now the generator will initialize the default layout and install all its dependencies.

Setting up Travis CI

When the installation has completed, make sure everything got set up properly by running:

$ npm test

This will run some basic generator tests and output the result on the console.

This command can also be used to automatically run the tests on a continuous integration server. There are many open source CI servers, the most popular being Jenkins, and many hosted solutions like Travis CI.

We will use Travis CI as our hosted solution because it is free for open source projects. It works with your GitHub account which it will use to sign up. Once signed in, go to Accounts (in the dropdown under you name) to enable the donejs-<username>-jshint repository:

generator-travis

You may have to click the "Sync account" button for the repository to show up. Now, every time we push to GitHub the tests will run automatically. We can do so with our initial commit:

$ git add . --all
$ git commit -am "Initial commit"
$ git push origin master

If you now go https://travis-ci.org/<your-username>/donejs-<username>-jshint/builds you will see the build running and eventually turn green (which will update the badge that got added in the readme.md file).

Adding the configuration files

Now we can add the files that our generator should produce. All file templates will be put in the default/templates/ folder.

.jshintrc

First, add a default/templates/.jshintrc file which contains options for JSHint:

{
  "node": true,
  "esnext": true,
  "bitwise": true,
  "camelcase": true,
  "curly": true,
  "eqeqeq": true,
  "immed": true,
  "indent": 2,
  "latedef": "nofunc",
  "newcap": false,
  "noarg": true,
  "regexp": true,
  "undef": true,
  "unused": true,
  "strict": false,
  "trailing": true,
  "smarttabs": true,
  "white": false
}

.editorconfig

Next add a default/templates/.editorconfig file like this:

; Unix-style newlines
[*]
end_of_line = LF
indent_style = <%= indent_style %>
trim_trailing_whitespace = true

All files support EJS placeholders. Here, <%= indent_style %> will be used for the user choice of using whitespaces or tabs. Finally, remove defaults/templates/file.js since we won't be using it.

Implementing the generator

For the most part, DoneJS generators are simply Yeoman generators so everything documented for writing your own Yeoman generator also applies here. For the user choice of tabs vs. spaces also refer to the chapter about interacting with the user.

Adding the generator functionality

Our generator needs to ask if we want to use spaces or tabs and then copy the .jshintrc and .editorconfig files over to their final destination. We also want to add an npm run jshint script to the package.json and make sure that JSHint runs during npm test. The complete generator at default/index.js looks like this:

var Generator = require('yeoman-generator');
var _ = require('lodash');

module.exports = Generator.extend({
  initializing: function () {
    // Read the original package.json
    this.pkg = this.fs.readJSON(
      this.destinationPath('package.json'), {}
    );
    
    // Maintain a list of all files we want to copy over
    this.files = [
      '.editorconfig',
      '.jshintrc'
    ];
  },
  
  prompting: function () {
    var done = this.async();

    // Create a prompt setting the `indent_style` property
    // to `tab` or `space`
    this.prompt([{
      type: 'list',
      name: 'indent_style',
      message: 'What indentation style do you want to use?',
      default: 'tab',
      choices: [
        {
          name: 'Tabs',
          value: 'tab'
        },
        {
          name: 'Spaces',
          value: 'space'
        }
      ]
    }]).then(function (answers) {
      this.props = answers;
      done();
    }.bind(this));
  },
  
  writing: function () {
    var pkg = this.pkg;
    
    // Update `package.json` with the `jshint` command
    // and update the `test` script
    pkg.scripts = _.extend(pkg.scripts, {
      test: 'npm run jshint && ' + 
        _.get(pkg, 'scripts.test',
          'echo "No tests specified"'),
      jshint: 'jshint ' + 
        _.get(pkg, 'system.directories.lib',
          'src') + 
        '/. --config'
    });
    
    // Write to `package.json` and format accordingly
    // This will prompt you to overwrite
    var indent = this.props.index === 'tab' ? '\t' : '  ';
    this.fs.writeJSON('package.json', pkg, null, indent);

    // Install jshint as a development dependency
    this.npmInstall([ 'jshint' ], { saveDev: true});
    
    // Got through every file and copy it
    this.files.forEach(function(file) {
      this.fs.copyTpl(
        this.templatePath(file),
        this.destinationPath(file),
        this.props
      );
    }.bind(this));
  }
});

That's it. Now we have a fully functional generator and can give it a try in a DoneJS application.

Manual testing

When running donejs add <generatorname> DoneJS will

  • Check if donejs-<generatorname> is installed locally
  • If not, install it from npm
  • Then run the generator at default/index.js

If we want to test our generator without publishing it to npm first we can link it instead. In the generator folder run:

$ npm link

Then go into your test DoneJS application directory:

$ cd ../place-my-order
$ npm link donejs-<username>-jshint

Now we can run

$ donejs add <username>-jshint

Writing a unit test

Yeoman also comes with some tools for testing generators. The test we initially ran with npm test makes sure that default/templates/file.js gets written. Since we deleted that file, update the test at test/index.js to verify that it wrote the files we want with the content we expect:

var path = require('path');
var helpers = require('yeoman-test');
var assert = require('yeoman-assert');

describe('donejs-<username>-jshint', function() {
  before(function(done) {
    // Run the generator in a temporary directory
    helpers.run(path.join(__dirname, '../default'))
      .inTmpDir()
      // Mock the user input by setting
      // `indent_style` to `tab`
      .withPrompts({
        'indent_style': 'tab'
      }).on('end', done);
  });

  // Verify that `.jshintrc` got written
  // and has some content
  it('created .jshintrc', function() {
    assert.file(['.jshintrc']);
    assert.fileContent('.jshintrc',
      /"latedef": "nofunc"/);
  });
  
  // Verify that `.editorconfig` got written
  // with `indent_style` set to our selection
  it('.editorconfig with indent_style', function() {
    assert.file(['.editorconfig']);
    assert.fileContent('.editorconfig',
      /indent_style = tab/);
  });
  
  // Make sure that `package.json` got updated
  // with the `jshint` npm script
  it('update package.json', function() {
    assert.jsonFileContent('package.json', {
      scripts: {
        jshint: 'jshint src/. --config'
      }
    });
  });
});

Now we can see all tests passing when running:

$ npm test

Publishing the plugin

Making a pull request

Although we are working on the generator by ourselves for now, GitHub pull requests are a great way to keep track of our progress and to make sure that all tests are passing. In the plugin folder run:

$ git checkout -b generator-functionality
$ git add . --all
$ git commit -m "Implementing JSHint and editorconfig generator"
$ git push origin generator-functionality

And then create a new pull request by going to https://github.com/<username>/donejs-<username>-jshint which will now show an option like this:

generator-pr

Once you created the pull request, you will see a Some checks haven’t completed yet message that will eventually turn green:

generator-pull-request

Now you can click the "Merge pull request" button. Then in the console, checkout the master branch and pull down the latest changes with:

$ git checkout master
$ git pull origin master

Publishing to npm

For others to be able to use your generator via donejs add <generatorname> you have to publish it to npm. Create a new account and then log in via

$ npm login

Semantic versioning is a great way to communicate new features and breaking changes. The generated plugin already comes with the release scripts to publish new versions according to the major.minor.patch schema. In our case to publish an initial version 0.1.0 we can run

$ donejs release:minor

Now version 0.1.0 of the generator is available and everybody can use it through

donejs add <username>-jshint

Show it off

Once you published your generator, let the world know about it. Tweet @donejs and post it in the DoneJS forums and the DoneJS chat. Those are also great places to get quick help with any questions.