Skip to content

Instantly share code, notes, and snippets.

@timrourke
Last active August 29, 2015 14:24
Show Gist options
  • Save timrourke/670a7c12a342018be703 to your computer and use it in GitHub Desktop.
Save timrourke/670a7c12a342018be703 to your computer and use it in GitHub Desktop.
Build tools and task runners

The problem

The modern web developer's workflow is growing in complexity. This complexity is the partly the result of a growing ecosystem of codebases, tools and resources created to make writing code faster, cleaner, and less error-prone. In addition, market forces are pushing developers to get more done with less.

The solution to some of these problems can be found in automation. Automation is now a driving force in the development landscape. Using modern development tools, tasks that once took hundreds or thousands of lines of code or several hours of manual work to accomplish can now be reduced to a few terminal commands.

A modern web developer might use a task runner to accomplish the following:

  • Compile CSS into SASS/LESS
  • Check JavaScript files for errors
  • Concatenate all JS or CSS files into one big file for more efficient serving to a browser
  • Run a suite of tests to make sure your project is working correctly
  • Deploy your project to a server
  • Pretty much anything else you might want to automate

Examples of task runners

There are a number of popular task runners currently in use in the wild. The biggest players are the following:

  • Grunt.js - The old standby. Still very prevalent in production. Configuration over code. May be best for very detailed, customized tasks where speed is less critical.
  • Gulp.js - My favorite, uses nodejs streams for optimum performance, allowing parallel tasks to run asynchronously. Code over configuration.
  • Broccoli.js - Less common but good to know about.
  • CodeKit - With a simple GUI, has a lower barrier to entry but not ideal for teams and professional environments, somewhat limited functionality

Sass: A model use-case for task runners

Most modern front-end developers no longer write plain CSS to author their stylesheets. Sass and Less are now the preferred languages for styles. These languages are a superset of plain CSS, adding functionality and features that front end developers absolutely rely on to be efficient.

Unfortunately, browsers do not natively interpret Sass and Less code, so Sass and Less must be compiled into raw CSS to be useful on the web. By now, we should all be familiar with what Sass and Less are and why they are a much better choice for authoring styles. Using the compilation of Sass as our guiding sample use-case, we will look at examples of workflows simplified by CodeKit, Grunt, and then Gulp.

Setting up our Grunt example project

To get started, navigate to an empty project folder. For this example with Grunt, we'll call ours grunt-example-sass. At the terminal, type the following commands:

mkdir grunt-example-sass
cd grunt-example-sass

Let's get Sassy!

Obviously, for this example to work, we're also going to need some Sass files to compile. For this example, we can grab a copy of the Sass files that make up the Concise CSS Framework. You can download those files here: https://github.com/ConciseCSS/concise.css/archive/v2.1.0.zip

Download those files and unzip them into a folder called scss under the main `grunt-example-sass' folder.

unzip concise.css-master.zip  #Unzips the files. By default they will go into a folder called 'concise.css-master/'
mv concise.css-master/scss .  #Move the folder 'scss/' from 'concise.css-master/' to the current dir 'grunt-example-sass'
rm -r concise.css-master      #Delete 'concise.css-master/' - we don't need it

Inside the scss folder, you should see the following files:

base/
components/
concise.scss
helpers/
layout/

We'll be compiling concise.scss into a CSS file called styles.css.

Disclaimer: I've never used Concise CSS, so I cannot vouch for its quality as a CSS framework, nor do I recommend using one, ever, period. I am simply using this as an example set of Sass files to use.

A quick note about Node.js

Grunt, Gulp, and Broccoli all run on Node.js. To use these applications, you will need to have Node installed on your development machine. The Node.js website has an excellent cross-platform installer available for download.

In general terms, Node projects define their dependencies in a file called package.json. This file serves as a register of any external or third-party resources a project might need access to in order to run properly. Installing these packages via the command line is done using NPM, a package manager bundled into the Node.js installer.

Understanding these tools will allow you to follow along with the examples on your own computer. You will see NPM in use in the following examples, both to initialize a project and to install dependencies.

Grunt

To get started with Grunt, navigate to grunt-example-sass. At the terminal, type the following command:

npm init

NPM

What's NPM? NPM stands for Node Package Manager. It is a central depot for useful (and some not-so-useful) Node.js packages available for use in your projects. There are thousands of available packages for download, and they can be found here: NPM. npm is also the name of the CLI tool that interacts with the NPM registry.

The command npm init will initialize a new Node project scaffold in the current folder. It provides a step-by-step process for creating an empty project. Feel free to hit enter and skip through all the irrelevant questions. The important part of this step is that it will generate a neatly formatted package.json file.

Once the npm init proccess is complete, open the package.json file in your favorite text editor. If you quickly skipped through all the default questions and called the proeject grunt-example-sass, it should look something like this:

{
  "name": "grunt-example-sass",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

The package.json file is exactly what it sounds like - a JSON file containing information about the current package. Right now this project has no dependencies. Let's add the first, most important dependency. The following command will install Grunt, the task runner we'll be looking at first. At the command line, type the following command:

npm install --save-dev grunt

After a moment, you should see a big bunch of crazy stuff printed to your terminal that should look something like this:

npm WARN package.json grunt-example-sass@1.0.0 No description
npm WARN package.json grunt-example-sass@1.0.0 No repository field.
npm WARN package.json grunt-example-sass@1.0.0 No README data
grunt@0.4.5 node_modules/grunt
├── which@1.0.9
├── dateformat@1.0.2-1.2.3
├── getobject@0.1.0
├── eventemitter2@0.4.14
├── rimraf@2.2.8
├── colors@0.6.2
├── async@0.1.22
├── hooker@0.2.3
├── grunt-legacy-util@0.2.0
├── exit@0.1.2
├── nopt@1.0.10 (abbrev@1.0.7)
├── lodash@0.9.2
├── minimatch@0.2.14 (sigmund@1.0.1, lru-cache@2.6.5)
├── glob@3.1.21 (inherits@1.0.0, graceful-fs@1.2.3)
├── coffee-script@1.3.3
├── underscore.string@2.2.1
├── iconv-lite@0.2.11
├── findup-sync@0.1.3 (glob@3.2.11, lodash@2.4.2)
├── grunt-legacy-log@0.1.2 (grunt-legacy-log-utils@0.1.1, underscore.string@2.3.3, lodash@2.4.2)
└── js-yaml@2.0.5 (esprima@1.0.4, argparse@0.1.16)

If you see a bunch of stuff like this, you've successfully installed Grunt.

npm install wha what?!

Let's break down the npm command we just wrote. This part is important, as it is key to installing any Node dependency.

Open up your package.json file in your text editor. What changed? You should see a new nested object in your file defining the new reliance on the Grunt package, looking something like this:

  "devDependencies": {
    "grunt": "^0.4.5"
  }

So, from this behavior, we can deduce the following:

  • npm is the CLI program that interacts with NPM
  • install is the piece of functionality we use from NPM to install a particular package
  • grunt is obviosuly the package we want to install
  • --save-dev saves the package as a dependency required for development

Roughly summed up, npm install --save-dev grunt installs Grunt from NPM, and handily modifies our package.json file so that future users of our project can refer to a list of packages they'll need to download and install in order to run our program later.

It is also possible to install a package using npm install --save ['package name goes here']. This will save a dependency to your package.json file, tagging it as something that is required for your package to actually run in production, versus for use during development, like when something might be required for access on a web server for real users. Typically Grunt is only required for development tasks, not production ones.

OK, OK. Back to Grunt, right?

We've successfully installed Grunt. Now we need to install some Grunt plugins. By itself, Grunt is useful, but to really take avantage of the tool, we can harness the power of a multitude of pre-written Grunt plugins. Since we intend to process Sass into CSS, we'll be looking specifically as just one plugin, called grunt-contrib-sass.

The plugin grunt-contrib-sass uses the Ruby version of Sass. For the newer, faster, less stable libsass, see grunt-sass.

Let's install our Grunt Sass plugin. At the terminal, type the following:

npm install --save-dev grunt-contrib-sass

In a couple seconds, you should see a nice report indicating that the install worked just great. Now open your package.json file again. You should also notice a new line added to your devDependencies block referring to grunt-contrib-sass:

"devDependencies": {
    "grunt": "^0.4.5",
    "grunt-contrib-sass": "^0.9.2"
  }

As you might have guessed, that string of numbers following each package indicates the version number. NPM uses a version numbering system called semver. You can read more about what that all means in an NPM context here. In the long run, the implication is that it is both possible and desirable to be intentional about the specific version of a dependency you define in your project. What might work on version 0.8.3 might completely ruin your day at version 0.8.7.

In addition to adding lines to our package.json file, installing each NPM package will also download the source for each package into a subfolder of your project called node_modules. At any point in development, you can take a peak under the hood of each package to get a feel for how it's made, how to best use it, and what those dependencies themselves depend on.

Creating our first Grunt task

In addition to installing Grunt and our Grunt plugin grunt-contrib-sass, we now need to create a file called Gruntfile.js. This file will define how we intend to use each task, defining the configuration for each task, and even creating our own tasks.

Create that file now:

touch Gruntfile.js

In a text editor, add the following code:

//module.exports is what node hooks onto to find the executable code for our programs.
//Note that we're passing a grunt object into this function.
module.exports = function(grunt) {
    //This grunt.initCongig() function will wrap our Grunt tasks, and will let us
    //define what tasks to run in what order.
    grunt.initConfig({
        //This line tells our Grunt package to use package.json as the central reference
        //for our project.
        pkg: grunt.file.readJSON('package.json'),

        //We can define each task by using the below syntax.
        //Here we define one task called 'sass' that we can run.
        //Because of the way that Grunt works, we must use the exact word
        //'sass' to define this task, so be sure to refer to each Grunt plugin's
        //GitHub project page for documentation on how to set up each task.
        sass: {
            // Our configuration for the 'sass' task will go here.
        }

    });

    //Here, we actually reference the 'grunt-contrib-sass' NPM package.
    //This loads the source for each Grunt plugin we intend to use.
    grunt.loadNpmTasks('grunt-contrib-sass');
 
    //Here we register a task called 'default'. The 'default' task is what is invoked
    //if we simply type 'grunt' at our terminal prompt. This is useful for defining the
    //main tasks you expect to use regularly for a particular project.
    grunt.registerTask('default', ['sass']);

};

Here is a link to this empty Gruntfile.js scaffold.

Right now this task won't actually do anything. We need to configure our sass task with some options so Grunt knows what to do. Remember those *.scss files we downloaded from the Concise CSS project? We're going to use the grunt-contrib-sass task to intelligently compile the Sass into CSS that our browser can interpret.

Let's define the required configuration in our Gruntfile.js. We'll use an example config block from the grunt-contrib-sass GitHub project page's docs as our guide. Add the following to your sass task definition object so it looks like the listing below:

sass: {                              // Task
    dist: {                            // Target
      options: {                       // Target options
        style: 'expanded'
      },
      files: {                         // Dictionary of files
        'styles.css': './scss/concise.scss'       // 'destination': 'source'
      }
    }
  }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment