Skip to content

Workflow with Yeoman, Grunt and Bower

Béla Varga edited this page Jan 16, 2014 · 28 revisions

image

Table of Contents

Grunt

grunt-contrib-clean (v0.5.0) Clean files and folders.

grunt-contrib-compress (v0.5.3) Compress files and folders.

grunt-contrib-concat (v0.3.0) Concatenate files.

grunt-contrib-connect (v0.5.0) Start a connect web server.

grunt-contrib-copy (v0.4.1) Copy files and folders.

grunt-contrib-jasmine (v0.5.2) Run jasmine specs headlessly through PhantomJS.

grunt-contrib-jshint (v0.7.2) Validate files with JSHint.

grunt-contrib-uglify (v0.2.7) Minify files with UglifyJS.

grunt-contrib-watch (v0.5.3) Run predefined tasks whenever watched files changed.

See all Grunt Plugins

Installation

//wrong way npm install -g grunt
npm install -g grunt-cli

Preparation and run grunt

mkdir example01
cd example01
grunt // Fatal error: Unable to find local grunt.

Install grunt locally and run again

npm install grunt
grunt // Fatal error: Unable to find Gruntfile.

Create Gruntfile.js

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({});

  // A very basic default task.
  grunt.registerTask('default', 'Log some stuff.', function() {
    grunt.log.write('Logging some stuff...').ok();
  });

};

or with grunt init https://github.com/gruntjs/grunt-init-gruntfile

grunt init:gruntfile

Remove your local grunt and add package.json

{
  "name": "my-project-name",
  "version": "0.1.0",
  "devDependencies": {
    "grunt": "~0.4.0",
    "grunt-contrib-jshint": "~0.7.2",
    "grunt-contrib-concat": "~0.3.0",
    "grunt-contrib-uglify": "~0.2.7"
  }
}

Install it again ;)

npm install

Add Grunt Modules to package.json

{
  "name": "my-project-name",
  "version": "0.1.0",
  "devDependencies": {
    "grunt": "~0.4.0",
    "grunt-contrib-jshint": "~0.7.2",
    "grunt-contrib-concat": "~0.3.0",
    "grunt-contrib-uglify": "~0.2.7"
  }
}
  • JSHint for style checking and code analysis.
  • Concat for file concatenation (for modules).
  • JSUglify for minifying javascript files.

More Informations:

http://www.jshint.com/blog/jshint-3-plans/ https://github.com/mdevils/node-jscs https://github.com/mishoo/UglifyJS2 Change your grunt file

// Project configuration.
/*global module:false*/
module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({

    jshint: {
      src: {
        options: {
          curly: false,
          undef: true
        },
        files: {
          src: ['../app/src/app.js', '../app/src/lib.js']
        }
      }
    },

    concat: {
      build: {
        options: {
          separator: ';',
        },
        src: ['../app/src/jquery.js' ,'../app/src/app.js', '../app/src/lib.js'],
        dest: '../app/build/app.js'
      }
    },

    uglify: {
      build: {
        files: { '../app/build/app.min.js': [ '../app/build/app.js' ] }
      }
    }

  });

  // Load task modules.
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');

  // Default task.
  grunt.registerTask('default', 'jshint');

};

Run grunt

grunt // jshint task is logging errors

Fix JSHint problems with adding globals to options

globals: {
  'window': true,
  'jQuery': true
}

Add more task to the default task

grunt.registerTask('default', ['jshint', 'concat', 'uglify']);

Change task names:

grunt.registerTask('default', ['test']); // grunt
grunt.registerTask('test', ['jshint']); // grunt test
grunt.registerTask('build', ['test', 'concat', 'uglify']); // grunt build

Install new module

npm install grunt-contrib-connect --save-dev

Update Gruntfile

connect: {
  server: {
    options: {
      port: 9001,
      base: '../app'
    }
  }
}
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.registerTask('server', 'connect');

Run grunt

grunt // server is down after task finished

Change options to run server

keepalive: true

Add option to automatically open browser at server start

open: true

Install new module

npm install grunt-contrib-watch --save-dev
npm install grunt-contrib-connect --save-dev
npm install connect-livereload --save-dev

Update Gruntfile

connect: {
  server: {
    options: {
      port: 9000,
      base: '../app',
      hostname: 'localhost', // Change to 0.0.0.0 to external connection.
      open: true // Open default browser window.
    }
  }
}
watch: {
  scripts: {
    files: [ '../app/src/*.js' , '../app/index.html' ],
    tasks: [] // Add more tasks here.
  },
}
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('default', 'server');
grunt.registerTask('server', ['connect', 'watch']);

Add live reload with new connect middleware

middleware: function (connect, options) {
  return [
    require('connect-livereload')({ port: 35729 }),
    // Serve static files.
    connect.static(options.base)
  ];
}

Active live reload with watch task

options: {
  livereload: 35729
}

Add Jasmine and PhantomJS

npm install grunt-contrib-watch --save-dev
npm install grunt-contrib-jasmine --save-dev
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-jasmine');

Change path to pivotal example tests.

path: '../app/test-jasmine'

Change configuration

watch: {
  scripts: {
    files: '<%= path %>/src/**/*.js',
    tasks: ['jasmine']
  },
},

jasmine: {
  pivotal: {
    src: '<%= path %>/src/**/*.js',
    options: {
      specs: '<%= path %>/spec/*Spec.js',
      helpers: '<%= path %>/spec/*Helper.js'
    }
  }
}

Set default task and map test task to jasmine

grunt.registerTask('default', 'watch');
grunt.registerTask('test', 'jasmine');

Run grunt test

grunt test

Add code coverage report with istanbul (based on Esprima)

npm install grunt-template-jasmine-istanbul --save-dev

Change Jasmine options

template : require('grunt-template-jasmine-istanbul'),
templateOptions: {
  coverage: 'reports/coverage.json',
  report: 'reports/coverage'
}

For Grunt, Jasmine and RequireJS look at this example: https://github.com/jamespamplin/grunt-contrib-jasmine-requirejs-example

grunt taskName:targetName

Create task configuration with two targets.

grunt.initConfig({
  task: {
    targetFoo: [1, 2, 3],
    targetBar: true
  }
});

Register this task as multi task.

grunt.registerMultiTask('task', 'Log stuff.', function() {
  grunt.log.writeln(this.target + ': ' + this.data);
});

Run grunt

grunt task:targetFoo // runs the task with the selected target
grunt task // goes over all targets

Add async task

grunt.registerTask('taskAsync', 'Async task.', function() {
  // Force task into async mode and grab a handle to the "done" function.
  var done = this.async();
  // Run some sync stuff.
  grunt.log.writeln('Processing task...');
  // And some async stuff.
  setTimeout(function() {
    grunt.log.writeln('All done!');
    done();
  }, 1000);
});

Test execution order

grunt.registerTask('run', ['taskAsync', 'task:targetBar', 'taskAsync', 'task:targetFoo']);
grunt run
  1. Install grunt-init with npm install -g grunt-init
  2. Install the gruntplugin template with git clone git://github.com/gruntjs/grunt-init-gruntplugin.git ~/.grunt-init/gruntplugin
  3. Run grunt-init gruntplugin in an empty directory.

  1. Run npm install to prepare the development environment.
  2. Author the plugin. Change tasks/token_count.js
'use strict';

module.exports = function(grunt) {

  grunt.registerMultiTask('token_count', 'Parse JavaScript and count tokens.', function() {
    
    var esprima = require('esprima');

    var histogram = {
        Boolean: 0,
        Identifier: 0,
        Keyword: 0,
        Null: 0,
        Numeric: 0,
        Punctuator: 0,
        RegularExpression: 0,
        String: 0
    };

    // Iterate over all specified file groups.
    this.files.forEach(function(f) {
      // Concat specified files.
      var src = f.src.filter(function(filepath) {
        // Warn on and remove invalid source files (if nonull was set).
        if (!grunt.file.exists(filepath)) {
          grunt.log.warn('Source file "' + filepath + '" not found.');
          return false;
        } else {
          return true;
        }
      }).map(function(filepath) {

        grunt.log.writeln('Token count for : ' + filepath);

        // Read file source.
        var content = grunt.file.read(filepath),
            tokens = esprima.parse(content, { tokens: true }).tokens;

        tokens.forEach(function (token) {
            histogram[token.type] += 1;
        });

      });

      for (var type in histogram) {
          if (histogram.hasOwnProperty(type)) {
              grunt.log.writeln(type + ' : ' + histogram[type]);
          }
      }

    });
  });

};

Add esprima to the dependencies at the package.json

"dependencies": {
  "esprima": "~1.0.4"
}
  1. Create a local npm package.
cd grunt-token-count // folder where package.json exists

Create an tgz file

npm pack

Install the npm package from the tgz file

cd .. // path to your project
npm install grunt-token-count/grunt-token-count-0.1.0.tgz

Set the package to real private.

"private": true

Enter the jquery directory and install the Node dependencies, this time without specifying a global install:

cd jquery && npm install

Make sure you have grunt installed by testing:

grunt -version

Then, to get a complete, minified (w/ Uglify.js), linted (w/ JSHint) version of jQuery, type the following:

grunt

The built version of jQuery will be put in the dist/ subdirectory, along with the minified copy and associated map file.

Bower

image

Packets are loaded from an git endpoint and then cached locally. The packages are in this folder: yeoman-folder/components/

Versions of the packages are tags in the git repository.

"modernizr": "~2.6.1"

Example 01 - Basic usage

Example 02 - Create own package with version

Create a github repository with an component.json

{
 "name": "myProject",
 "version": "1.0.0",
 "main": ["./path/to/app.css", "./path/to/app.js"],
 "dependencies": {
   "jquery": "~1.7.2"
 }
}

Register it global

bower register myawesomepackagename git://github.com/foo/bar

Example 03 - Set up own server

node_modules/bower/lib/core/source.js:line12
https://bower.herokuapp.com/packages

Move folder /bower_server to an Webserver

This url

http://localhost/bower_server/packages/

should return back this json (needs php)

[{"name":"fun","url":"[email protected]:netzzwerg/bower-fun.git"},
{"name":"core","url":"[email protected]:netzzwerg/bower-core.git"},
{"name":"jquery","url":"git://github.com/netzzwerg/bower-jquery.git"}]

and you can change the url in /fec_build/.bowerrc

{
  "directory" : "packages",
  "json" : "component.json",
  "endpoint" : "http://localhost/bower_server"
}

bower search should you show only 4 results

cd fec_build
bower search

Yeoman

image

Installation

  ruby -e "$(curl -fsSkL raw.github.com/mxcl/homebrew/go)"
  brew install git optipng jpeg-turbo phantomjs
  brew link jpeg-turbo
  • Compass >= 0.12.1
  gem update --system
  gem install compass
  • Yeoman
  npm install yeoman -g

Check installation again

  curl -L get.yeoman.io | bash

Installationspath

  /usr/local/lib/node_modules/yeoman

Build Target Profiles

These are equivalent to grunt alias except that we defined a single task and use arguments to trigger the appropriate target.

  • build no html compression, no usemin-handler task
  • usemin (default) same as build but parsing config from markup
  • text same as usemin but without image (png/jpg) optimizing
  • buildkit minor html optimizations, all html whitespace/comments maintained (todo: inline script/style minified)
  • basics same as buildkit plus minor html optimizations
  • test same as default build plus but conditionally runs compass / manifest task depending on whether or not compass / phantomjs binaries are available within the path. During the checking process, we output warning infos about missing deps. It might make sense to make it the default (img task internally does this check)

Build Tasks

  • default rjs concat css min img rev usemin manifest
  • usemin usemin-handler rjs concat css min img rev usemin manifest
  • text usemin-handler rjs concat css min rev usemin manifest
  • buildkit usemin-handler rjs concat css min img rev usemin manifest html:buildkit
  • basics usemin-handler rjs concat css min img rev usemin manifest html:basics
  • minify usemin-handler rjs concat css min img rev usemin manifest html:compress
  • test usemin-handler rjs concat css min img rev usemin manifest

Example 01 - Basic usage

Show all existing generators.

yeoman init --help 

Generate Backbone project

yeoman init backbone

Start local webserver

yeoman server

Test the application with Mocha

yeoman test

Build the application

yeoman build

Add new backbone model

yeoman init backbone:model MYDATA

Example 02 - Build own generator

Show all existing generators.

yeoman init --help 

Generator path under MacOSX

/usr/local/lib/node_modules/yeoman/node_modules/yeoman-generators/lib/generators

generator-name/all/index.js

var path = require('path'),
    util = require('util'),
    yeoman = require('../../../../');

module.exports = Generator;

function Generator() {
  yeoman.generators.Base.apply(this, arguments);
  this.appname = path.basename(process.cwd());
}

util.inherits(Generator, yeoman.generators.Base);

Generator.prototype.createIndexFile = function createIndexFile() {
  this.template('index.html', 'app/index.html');
};

Generator.prototype.createCssFile = function createCssFile() {
  this.template('bootstrap.css', 'app/bootstrap.css');
};

Example 03 - Modify build process

Change your local grunt file:

grunt.registerTask('my-task', 'my grunt task', function() {
  console.log('my task');
});
grunt.registerTask('build', 'your own build task list', function() {
  //var tasks = 'clean mkdirs usemin-handler rjs concat min css rev usemin manifest copy time';
  var tasks = 'my-task time';
  grunt.task.run(tasks);
});

Example 04 - Add own plugin with task

module.exports = function(grunt){

  grunt.loadNpmTasks('your-grunt-plugin');

  grunt.initConfig({
    // grunt config
  });

  grunt.registerTask('default', 'your-task');
};
Clone this wiki locally