Monday, May 28, 2012

Using Grunt.js with CSS

For most of my apps, I've been using either a Makefile, shell scripts, Python scripts, or some combination of those as my build tool — taking care of tasks like concatenating files, minifying files, and linting my code. A few months ago, Ben Alman introduced Grunt, a build tool written in JS and designed for JS. I quite liked the idea of using JS for task automation, since it's a language I already know (bash scripting makes me want to bash my head against the wall) and it's the language that I write my apps in (at least the front ends). I finally got the opportunity to properly try it out last week, while porting my HTML5 slide editing app over from Closure to Bootstrap.

Grunt is designed primarily for JavaScript projects, like Node.JS or jQuery plugins, so it comes out of the box with tasks for concatenating files, linting JS files (via JSHint), compressing JS files (via UglifyJS), running a Node server, and running JS unit tests — no CSS-related tasks. For this web app, I decided to write the CSS using Less, a CSS pre-processor. While debugging locally, I include the .less file on the page and the less.js pre-processor, but for performance reasons on production, I wanted to just include a minified CSS file. So I needed 2 new tasks: one for processing the .less, and another for minifying the .css. Grunt has a formal way for developers to define and share new tasks via extensions, and luckily I found developers have already made grunt-less and grunt-css extensions.

To use the Grunt extensions, I first had to install them as node modules. I could have installed them simply by running npm install grunt-less inside my project folder, but at the suggestion of Ben, I instead created a package.json file with the modules listed as dependencies. Then I just ran npm install and it grabbed everything necessary. Now, since my code is open-source, I don't have to tell people what they need to install - just have to make sure they run install. Here's what my file looks like:

{
  "name": "SlideEditor",
  "version": "0.0.0",
  "dependencies" : {
    "grunt-less":  ">0.0.0",
    "grunt-css":   ">0.0.0"
  }
}

Once I did that, I just loaded the task definitions in my grunt.js file by sticking these lines in:

  grunt.loadNpmTasks('grunt-less');
  grunt.loadNpmTasks('grunt-css');

And now I could use the tasks! However, before I set them up, I spent a while figuring out the best directory structure for both my JS and CSS. When you're using any sort of build tool, it helps to have a sensible directory structure, like separate folders for the debug files versus the build files. For this app, I decided to put all the original files in a src folder with child folders for css and js, and for the build files, I just put them in css and js folders under the root. In the src/css folder, I have children folders less (for the .less file), app (for the processed less file), and libs (for any CSS files from 3rd party libraries). Here's what that looks like:

  • src
    • css
      • less
      • app
      • libs
    • js
      • app
      • libs
  • js
  • css

Okay, so finally, I put the Grunt extensions to work, setting up the less and cssmin tasks. Here's what all the CSS-related tasks look like in my grunt file:

  var SRC_CSS   = 'src/css/';
  var BUILD_CSS = 'css/';
  
  grunt.initConfig({
    // ...
    less: {
      css: {
        src: [SRC_CSS + 'less/base.less'],
        dest: SRC_CSS + 'app/base.css',
      }
    },
    concat: {
      css: {
        src: [SRC_CSS + 'libs/*.css',
              SRC_CSS + 'app/*.css'],
        dest: BUILD_CSS + 'css/all.css'
      }
    },
    cssmin: {
      css: {
        src: '',
        dest: BUILD_CSS + 'css/all-min.css'
      }
    },
    // ...
  });

You can check out my full grunt.js file here, to see how I use the CSS tasks and JS tasks together. Like any tool, Grunt takes a bit of time to learn, but at least for me, I find it much more approachable than the world of bash scripting. Try it out for your next project!

No comments: