gruntled-talk



gruntled-talk

0 1


gruntled-talk

Experimenting with using reveal.js for a talk about grunt

On Github NickHeiner / gruntled-talk

angular.js-dc

Feburary 2014

@nickheiner and @opower

(Many examples taken straight from the grunt docs)

Shameless plug time

  • Work with smart people to solve hard problems using cool tech, like node and angular

  • We've saved over 3.7TWH of energy so far, abating over 4.6 billion lbs of CO2 emissions

  • Free massages, scooters and dogs in the office, meme-friendly culture

Part 1

  • 〉 Using grunt in your own projects

  • Building your own grunt tasks

  • Best practices

why grunt?

  • No more terrible bash spaghetti messes

  • No more using a tool designed for another ecosystem, like rake

  • No more ambiguous, global dev dependencies

  • No more re-inventing the wheel for common cases like CLI parsing, logging, etc

  • No more IDE-specific build steps that lock devs into one editor

what can grunt do?

Grunt can do anything.

  • Cross-compile (scss -> css, coffee -> js, jade -> html, etc)
  • Copy files
  • Clean directories
  • Build (minify, browserify, requirejs, concat, etc)
  • Lint
  • Run tests
  • Launch local servers
  • Deploy

The only limit... is yourself.

demo time

how do I use grunt?

# Required to use grunt on your system
# You only need to do this once - better than gulp!
$ npm install -g grunt-cli

# Add grunt to the project you're current in
$ npm install --save-dev grunt

# Make a gruntfile
$ touch Gruntfile.js

gruntfile.js

module.exports = function(grunt) {

    grunt.initConfig({});

};

You can natively use .coffee as well.

gruntfile.js

$ npm install --save-dev grunt-contrib-jshint
module.exports = (grunt) ->

    grunt.loadNpmTasks 'grunt-contrib-jshint'

    grunt.initConfig
        jshint:
            options:
                globals:
                    angular: true
            files: ['scripts/**/*.js']
$ grunt jshint
Running "jshint:files" (jshint) task
>> 10 files lint free.

Done, without errors.

gruntfile

module.exports = (grunt) ->

    grunt.loadNpmTasks 'grunt-contrib-jshint'

    grunt.initConfig
        jshint:
            options:
                global:
                    angular: true

            scripts:
                files: ['scripts/**/*.js']

            test:
                files: ['test/**/*.js']
$ grunt jshint:scripts
Running "jshint:scripts" (jshint) task
>> 10 files lint free.

Done, without errors.
$ grunt jshint:test
Running "jshint:test" (jshint) task
>> 3 files lint free.

Done, without errors.

run multiple tasks

$ grunt coffeelint:all jshint:all buildIndex sass:dist connect:livereload
Running "coffeelint:all" (coffeelint) task
>> 1 file lint free.

Running "jshint:all" (jshint) task
>> 1 file lint free.

Running "buildIndex" task

Running "sass:dist" (sass) task
File styles/styles.css created.

Running "connect:livereload" (connect) task
Started connect web server on 127.0.0.1:9000.

Done, without errors.

chain tasks together

grunt.registerTask('test', 'verify that the code is sound', [
    'jshint',                   // static analysis
    'karma:unit',               // run unit tests via karma
    'launch-protractor-server', // launch a server for ptor tests to hit
    'protractor'                // run protractor tests
]);

grunt.registerTask('publish', 'test and publish to npm', [
    'test',         // be sure that we only publish good code
    'build',        // uglify, concat, whatever you need to do
    'shell:publish' // run `npm publish`
]);

non-task config

Config doesn't need to be task-specific

directories: {
    demo: 'dist'
},

// some made-up build task
build: {
    local: {
        src: 'scripts/**/*.js',
        dest: '<%= directories.demo %>'
    }
}

pulling in package.json

  pkg: require('./package'), // read package.json

  uglify: {
    options: {
      banner: '/* <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
    },
    // ... etc
  }

config options

jshint:
    options:               # default options for all targets
        node: true

    grunt:
        files: 'Gruntfile.js' # accept the defaults as-is

    test:
        options:           # extend the defaults
            force: true
        files: 'test/**/*.js'

    src:
        options:           # override and extend the defaults
            node: false,
            reporter: 'checkstyle'
        files: 'scripts/**/*.js'

file specific config

jshint: {

    globbing: {
        src: ['src/**/*.js']
    }

    srcDestPairs: {
        src: ['src/a.js', 'src/b.js'],
        dest: 'dest/c.js'
    }

}

file specific config

jshint: {

    filesObjectFormat: {
        'dest/c.js': ['src/a.js', 'src/b.js']
        'dest/d.js': ['src/e.js', 'src/f.js']
    },

    filesArrayFormat: [
        {src: ['src/aa.js', 'src/aaa.js'], dest: 'dest/a.js'},
        {src: ['src/aa1.js', 'src/aaa1.js'], dest: 'dest/a1.js'},
    ]

}

There are even more, but they are deprecated.

Part 2

  • Using grunt in your own projects

  • 〉Building your own grunt tasks

  • Best practices

register a task

grunt.registerTask(

    'verify-file-exists',                    // task title

    'Verify that a file still exists',       // optional task description

    function() {                             // the task itself

    }

);

register a task

grunt.registerTask(

    'verify-file-exists',                    // task title

    'Verify that a file still exists',       // optional task description

    function() {                             // the task itself

        var file = grunt.config('verify-file-exists');

        if (!grunt.file.exists(file)) {
            grunt.fail.fatal('Could not find ' + file);
            return;
        }

        grunt.log.ok(file + ' exists ;)')
    }

);

Configure your task

grunt.initConfig({

    'verify-package-json-exists': 'package.json'

});

register a multitask

grunt.registerMultiTask(

    'verify-file-exists',                   // task title

    'Verify that specified files exists',   // optional task description

    function() {                            // the task itself

        var file = grunt.task.current.options().file;

        if (!grunt.file.exists(file)) {
            grunt.fail.fatal('Could not find ' + file);
            return;
        }

        grunt.log.ok(file + ' exists ;)')
    }
);

Configure your task

grunt.initConfig

    'verify-package-json-exists':
        pkg:
            options:
               file: 'package.json'

        bower:
           options:
               file: 'bower.json'

        travis:
            options:
               file: '.travis.yml'

async tasks

var s3 = require('s3');
var util = require('util');

grunt.registerMultiTask('deploy_static_assets', function() {

    // tell grunt this may take a while
    var done = grunt.task.current.async();

    s3.push('dev.opower.com').then(function success() {
        // To indicate success, call done() with no arguments.
        done();
    }, function err(reason) {
        // To indicate failure, pass an Error to done.
        var reasonAsErr = util.isError(reason) ? reason : new Error(reason);
        done(reasonAsErr);
    });
});

async dangerzone

grunt.registerMultiTask('deploy-static-assets', function() {

    var done = grunt.task.current.async();

    s3.push('dev.opower.com');

    // oops - forgot to ever call done()
});
$ g exp
Running "exp" task
$

async dangerzone

grunt.registerMultiTask('deploy-static-assets', function() {
    var done = grunt.task.current.async();
    s3.push('dev.opower.com')
        .then(done, function onErr(reason) {
            // oops - calling done() with a value that is not an Error or false
            done('task failed');
        });
});
$ g exp
Running "exp" task

Done, without errors

configuring from the CLI

grunt.registerMultiTask('deploy_static_assets', function() {

    var done = grunt.task.current.async();
    var opts = grunt.task.current.options({
        bucket: 'dev.opower.com'
    });

    s3.push(opts.bucket).then(done, function err(reason) {
        done(util.isError(reason) ? reason : new Error(reason));
    });
});
grunt.initConfig({

    'deploy_static_assets': {
        dev: {
            options: {
                bucket: grunt.option('bucket')
            }
        }
    }

});
$ grunt deploy_static_assets:dev --bucket "qa.opower.com"

tasks on tasks on tasks

grunt.registerMultiTask('deploy', function() {

    if (!grunt.option('skip-tests')) {
        grunt.task.run('test');
    }

    if (grunt.option('minify')) {
        grunt.task.run('uglify');
    }

    grunt.task.run('deploy-static-assets');

});

Part 3

  • Using grunt in your own projects

  • Building your own grunt tasks

  • 〉Best practices

load-grunt-tasks

Use @sindresorhus's excellent load-grunt-tasks library.

Before:

grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-mochatest');
grunt.loadNpmTasks('grunt-doge-script');
grunt.loadNpmTasks('grunt-s3');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-coffeelint');
grunt.loadNpmTasks('grunt-my-task');
grunt.loadNpmTasks('grunt-bitcoin-miner');
grunt.loadNpmTasks('grunt-dogecoin-miner');
grunt.loadNpmTasks('grunt-available-tasks');

After:

require('load-grunt-tasks')(grunt)

npm test = grunt test

Gruntfile.js

grunt.registerTask('test', ['lint', 'unit', 'e2e']);

package.json

"scripts": {
    "test": "grunt test"
}
$ npm test

> gruntled@0.1.0 test /Users/nick.heiner/public-projects/gruntled-talk
> grunt test

Running "coffeelint:all" (coffeelint) task
>> 1 file lint free.

Running "jshint:all" (jshint) task
>> 1 file lint free.

Done, without errors.

use valid identifiers for task names

Bad:

'deploy-static-assets': 
  'dev-or-qa':
    options:
      bucket: 'dev.opower.com'

'verify-static-assets': 
  'dev-or-qa':
    options:
      bucket: '<%= grunt.config(["deploy-static-assets", "dev-or-qa"]).options.bucket %>'

Good:

deploy_static_assets:
  ಠ_ಠ:
    options:
      bucket: 'dev.opower.com'

verify_static_assets:
  dev_or_qa:
    options:
      bucket: '<%= deploy_static_assets.ಠ_ಠ.options.bucket %>'