User Tools

Site Tools


Action disabled: register
entwine:understanding_grunt

Understanding Grunt

It's OK if you don't understand everything in this section. The examples later in this guide explain exactly how to customize them for your needs, and you can get by just fine without understanding things beyond that. This explains the ideas behind Grunt, in case you are confused by something.

Grunt works by looking at a special file in your project folder named Gruntfile.js. It's picky about this. Gruntfile.js must be at the top level of your project folder, and it has to be named exactly “Gruntfile.js”, with that capitalization.

This file is JavaScript code, so let's take a brief break to make sure you understand:

Enough JavaScript To Get By

There are six key JavaScript concepts you'll need to know to understand Grunt. First, JavaScript has values. These come in three basic flavors: numbers, Boolean values, and strings. Numbers are self-explanatory; Boolean values are either true or false; and strings are collections of letters, numbers, and punctuation, like 'The quick brown fox leaped over the lazy dog.' Strings are always surrounded by either single or double quotation marks (you can use either, so long as you're consistent) while numbers and Boolean values are just written by themselves.

Second, JavaScript has functions. They're like mathematical functions: they take in values and return a result. They're written like this:

function sum(a, b) {
  return a + b;
}

Parentheses surround the values the function takes in, and curly braces surround the instructions in the function.

Third, JavaScript has arrays. Arrays are just lists of values in sequence.

['Africa', 'Antartica', 'Asia', 'Australia', 'Europe', 'North America', 'South America']

Square brackets surround an array, and commas separate the values. You can mix different kinds of values in an array. ['Africa', 16] is perfectly fine to write.

Fourth, JavaScript has objects. Objects are like arrays, except instead of being in a sequence, they're more like a table.

{
  name: 'Fido',
  age: 4,
  friendly: true
}

Curly braces surround an object, and colons separate an object's keys from its properties. Object keys are always strings, but you can leave off the quotation marks so long as the string only contains letters.

You're allowed to nest objects and arrays in JavaScript, like this:

{
  name: 'Fido',
  'also answers to': 'Good dog',
  age: 4,
  friendly: true,
  commandsUnderstood: [
    'fetch',
    'heel',
  ]
}

Notice how you're also allowed to spread out a definition of both arrays and objects across multiple lines. It's a good idea to indent these definitions so you can make sure that you keep the number of {s and }s equal.

Fifth, JavaScript lets you add comments so you can leave notes for yourself as to how things work. Anything inside a comment is ignored when the code is run. Comments come in two forms:

// Two forward slashes mark the rest of a line as a comment.
 
/*
A forward slash and asterisk start a comment that can span multiple lines.
The reversed form ends it.
*/

Finally, JavaScript lets you include modules in your code by writing:

require('my-math');

And every module states what it will make available to other modules by writing:

module.exports = {
  name: 'Fido',
  'also answers to': 'Good dog',
  age: 4,
  friendly: true,
  commandsUnderstood: [
    'fetch',
    'heel',
  ]
 
  // And perhaps we'd include information about other dogs.
};

We only use modules in very small ways, so we won't go into any more detail than that.

Grunt plugins

Most of the time, you'll use a Grunt plugin to run a task for you. A plugin is a module that's installed with npm that will actually do the work for you. For example, the grunt-contrib-copy plugin copies files from one place to another. But it's up to your Gruntfile.js to tell it exactly what files to copy.

A Basic Gruntfile

Gruntfiles always follow a basic pattern. Here's an annotated example of one.

Gruntfile.js
// We make a single function available to Grunt. Grunt will call it
// so that we can tell it about our tasks.
 
module.exports = function(grunt) {
 
  // We ask the load-grunt-tasks module to set up our
  // plugins for us. The module exports a function that we hand
  // off Grunt to.
 
  require('load-grunt-tasks')(grunt);
 
  // Now, we tell Grunt about our tasks by calling one of its
  // functions with an object.
 
  grunt.initConfig({
 
    // Tasks are grouped by the plugin they belong to. Each
    // plugin looks for a specific key. Let's pretend we have a 
    // plugin that bakes cookies for us, and looks for a key
    // named 'bakeCookies'.
 
    bakeCookies: {
 
      // Inside this object, we tell the plugin about each task
      // we would like it to potentially perform. The sequence
      // doesn't matter; we'll define that later. Let's pretend
      // the bakeCookies task knows generally how to bake cookies -- 
      // we just need to tell it how hot to bake them, for how long,
      // and if there are any extra ingredients to add.
 
      chocolateChip: {
        temperature: 350,
        time: 10,
        extraIngredient: 'chocolate chips'
      }
    },
 
    // Now we're back up to the top level of the object,
    // so we can start defining tasks for other plugins. Here's
    // a definition for another imaginary plugin named cleanKitchen.
    // Inside that, we define three tasks for the different types of
    // cleaning we might want to do.
 
    cleanKitchen: {
      countertops: {
        tool: 'washcloth'
      },
 
      floor: {
        tool: 'mop'
      },
 
      oven: {
        tool: 'scrubbing pad'
      }
    }
  });
 
  // Grunt also lets us define tasks as sequences of other tasks.
  // The task below will run all of the bakeCookies plugin's tasks,
  // then the cleanKitchen tasks.
 
  grunt.defineTask('bakeAndClean', ['bakeCookies', 'cleanKitchen']);
 
  // We can be more specific about what tasks to run by adding the task name
  // preceded by a colon.
 
  grunt.defineTask('bakeAndCleanLazy', ['bakeCookies', 'cleanKitchen:countertops']);
 
  // Finally, we can define a default task to run if we don't tell Grunt
  // what we want to do from the terminal window.
 
  grunt.defineTask('default', ['bakeCookies', 'cleanKitchen:countertops', 'cleanKitchen:oven']);
};

With this Gruntfile, we could then type in these commands in the terminal window:

Typing… … does this:
grunt runs the default task, e.g. the sequence:
bakeCookiescleanKitchen:counterTopscleanKitchen:oven
grunt cleanKitchen runs all tasks belonging to the bakeCookies plugin
grunt cleanKitchen:floor runs just the floor tasks belonging to the cleanKitchen task
grunt bakeCookies cleanKitchen runs all bakeCookies tasks, then all cleanKitchen tasks

In practice, you usually only run the default task or a few specific ones, but Grunt offers a lot of flexibility if you need it.

Troubleshooting

Below are some common error messages you might see when using Grunt.

A valid Gruntfile could not be found.

Make sure your Gruntfile.js has the correct name and is at the very top level.

» SyntaxError: missing ) after argument list

If you see this, or anything else starting with SyntaxError, then your Gruntfile has the programming equivalent of a grammatical mistake. Most often, you're missing a piece of punctuation – for example, commas between items in an array. It's also common to miss an closing parenthesis, quotation mark, bracket, or curly brace.

entwine/understanding_grunt.txt · Last modified: 2017/10/09 20:39 (external edit)