Monday, March 10, 2014

Amazing Builds with Grunt.js

Continuing with my previous post on my new app Urban Champ, I recently optimized my build process using Grunt.js.

Grunt is an amazing build tool which can be used to do a variety of things. Currently I am using Grunt tasks for concatenating and minifying css files, running the RequireJS build optimizer, preprocessing HTML (to do things like point your built HTML at compiled/minified scripts), and image minification. Grunt is also extremely useful for separating your build targets (such as development and production) and then firing up a server on your localhost. I really like the livereload grunt "watch" task which will automatically reload your server as you change source files in your editor.

My hats off to the Grunt team! I will definitely be keeping a close eye on future Yeoman generators using Grunt. This is an extremely powerful tool for all you web devs out there!

Node grunt packages installed:
{
  "name": "UrbanChamp",
  "version": "0.1.0",
  "devDependencies": {
    "grunt": "~0.4.1",
    "grunt-contrib-concat": "^0.3.0",
    "grunt-contrib-uglify": "^0.4.0",
    "grunt-contrib-cssmin": "^0.9.0",
    "grunt-contrib-requirejs": "^0.4.3",
    "grunt-contrib-clean": "^0.5.0",
    "grunt-open": "^0.2.3",
    "grunt-contrib-connect": "^0.7.1",
    "grunt-contrib-watch": "^0.5.3",
    "grunt-contrib-livereload": "^0.1.2",
    "grunt-uncss": "^0.2.0",
    "grunt-processhtml": "^0.3.0",
    "connect-modrewrite": "^0.6.3-pre",
    "grunt-contrib-imagemin": "^0.5.0"
  }
}

Gruntfile.js:
'use strict';
var modRewrite = require('connect-modrewrite');
var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;
var mountFolder = function (connect, dir) {
    return connect.static(require('path').resolve(dir));
};


module.exports = function (grunt) {
    grunt.initConfig({
      clean: {
        build: {
          src: ['build']
        },
        css: {
          src: ['build/App/css/*.css','!build/App/css/tidy.min.css']
        },
        images: {
          src: ['build/App/images/*']
        }
      },
      watch: {
          livereload: {
              files: [
                  'app/App/*.html',
                  '{.tmp,app}/App/css/{,*/}*.css',
                  '{.tmp,app}/App/js/{,*/}*.js',
                  'app/App/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
              ],
              tasks: ['livereload']
          }
      },  
      connect: {
          options: {
              port: 8888,
              // change this to '0.0.0.0' to access the server from outside
              hostname: 'localhost'
          },
          livereload: {
              options: {
                  middleware: function (connect) {
                      return [
                          modRewrite(
             ['!\\.html|\\.js|\\.svg|\\.css|\\.png|\\.jpg$ /index.html [L]']),
                          lrSnippet,
                          mountFolder(connect, '.tmp'),
                          mountFolder(connect, 'app')
                      ];
                  }
              }
          },
          dist: {
              options: {
                  middleware: function (connect) {
                      return [
                          mountFolder(connect, 'build')
                      ];
                  }
              }
          }
      },          
      open: {
          server: {
              path: 'http://localhost:8888'
          }
      },      
      requirejs: {
        dist: {
         options: {
           appDir: 'app',
            baseUrl: 'App/js', 
            optimize: 'uglify',
            preserveLicenseComments: false,        
            mainConfigFile: 'app/App/js/main.js',
            dir: 'build'
         }
        }
      },
      concat: {
        dist: {
          src: ['app/App/css/*'],
          dest: 'build/App/css/tidy.css'
        }
      },
      cssmin: {
          dist: {
            src:'build/App/css/tidy.css',
            dest: 'build/App/css/tidy.min.css'
          }
      },      
      processhtml: {
        dist: {
          files: {
            'build/index.html': ['app/index.html']
          }
        }
      },
      imagemin:{
        dynamic: {
          files: [{
            expand: true,                  // Enable dynamic expansion
            cwd: 'app/App/images',                   // Src matches are relative to this path
            src: ['**/*.{png,jpg,gif}'],   // Actual patterns to match
            dest: 'build/app/images'                  // Destination path prefix
          }]          
        }

      }            
});


// load plugins
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-requirejs');
grunt.loadNpmTasks('grunt-open');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-processhtml');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-imagemin');


// register at least this one task
grunt.registerTask('build', ['clean:build','requirejs', 'concat','cssmin','processhtml', 'clean:css', 'clean:images', 'imagemin']);
grunt.registerTask('default', ['build']);

grunt.registerTask('server', function (target) {
    if (target === 'dist') {
        return grunt.task.run(['build', 'open:server', 'connect:dist:keepalive']);
    }

grunt.task.run(['open:server', 'connect:livereload:keepalive', 'watch']);
});
};