George McIntosh

I do something...with software...

Taming Grails Frontend Code With Grunt Pt. 2

This post follows on from my previous post.

First of all, an apology. In my last post on the subject, I left you with a bug. Did you spot it?

The event hook put into _Events.groovy works just fine if you’re running the app; everything works as planned, and, more pertinently, exits when the app does. Not so if you run tests.

1
$ grails test-app

Leaves orphaned Grunt processes hanging around, clogging things up and - as we found - hammering your CI server. Let’s fix it:

Currently, our _Events.groovy looks something like this

eventCompileStart = { msg ->
    def process = "grunt".execute()
    process.comsumeProcessOutput(System.out, System.err)
}

Yeh, that leaks processes. Sorry. Easily solved, though, with a shutdown hook. Try this instead:

eventCompileStart = { msg ->
    final def process = "grunt".execute()
    process.comsumeProcessOutput(System.out, System.err)
    Thread shutdown = new Thread(
        new Runnable() {

           @Override
           void run() {
              process.destroy()
           }

        }
    )

    Runtime.runtime.addShutdownHook(shutdown)
}

There. Fixed it. Actually, I’m in two minds about using Grails event hooks for this sort of thing at all, but that’s for another time.

This post is now about Bower.

As I hinted at not being a massive fan of Grails plugins for everything. Don’t get me wrong, they’re often really wonderful things, that get you integrating something sexy into your Grails app with little fuss. The ElasticSearch plugin, for example, does a lot of heavy lifting for you. But sometimes, as with the sass compiler, they’re more hassle than they’re worth. It really makes no sense for the back-end of your app to dictate what front-end libraries you can use. So today I’m going to stop using the JQuery plugin, and start providing JQuery myself. And in the spirit of using front-end tools for front-end work, I’m going to manage that dependency with Bower. If you don’t know, Bower is a dependency management tool for webapps. It uses a manifest file to describe dependencies, and fetches them from repositories on demand. You know the drill.

Create a file called bower.json, that looks like this

{
  "name": "shiny-shiny",
  "version": "0.0.0",
  "homepage": "https://github.com/georgecodes/shiny-shiny",
  "authors": [
    "George McIntosh <george@georgecodes.com>"
  ],
  "license": "MIT",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ]
}

If you have Bower installed, you can now run

1
$ bower install jquery#1.9.1 --save

and JQuery will be fetched from ‘t’internet, bunged into a folder called bower_components and an entry will be created in bower.json to boot. Handy, eh? No, not really. Not yet, anyway. That’s ok, we’re going to use some Grunt magic to make this dependency available to our app at runtime.

Grunt is a bit like Maven. Except you don’t want to kill yourself within five minutes of using it. But it’s like Maven in that it all revolves around plugins. We’re going to use one to grab the goodies from our JQuery package, and provide it to our app, in a layout we like. We could just tell Bower to change the directory it downloads dependencies to, but Bower dependencies are not just the distribution, they come with a bunch of other stuff. Mostly because currently, Bower just grabs git repos. So we’re going to use a bit of config to achieve that. But first, we need the right package. Here’s where people sometimes get a bit confused. We’re actually dealing with two dependency management packages at once here: NPM and Bower. NPM is what we’re using to manage dependencies for our build system, and Bower is what we’re using to manage dependencies for the front-end of our app. Of course, we’re also using BuildConfig to manage dependencies for the back-end of the app, which makes for three different dependency management thingies. Maybe Maven isn’t so bad after all no no no, I never said that.

Anyways, we’re giving our grunt build system something to do, so we’re using NPM to declare these particular dependencies. Update your package.json so it looks like this

{
    "name": "shiny-shiny",
    "version": "0.0.1",
    "devDependencies": {
        "node-sass": "~0.7.0",
        "grunt": "~0.4.1",
        "grunt-sass": "~0.8.0",
        "grunt-contrib-watch": "~0.5.3",
          "grunt-bower-task": "git://github.com/georgecodes/grunt-bower-task.git#master"
    }   
}

Note that the most recent grunt-bower-task brings in an outdated, buggy version of grunt that’s annoying, so I’ve forked it and fixed it. I’ve issued a pull request, but at time of writing it hasn’t been accepted. Haha disregard that, it’s fixed. Or maybe NPM allows fine-grained control of transitive dependencies, like Maven does. I didn’t find anything about it, feel free to correct me.

Remember I said that Bower packages are just the entire Git repo? Well, we don’t want that, we just want the JQuery distribution. Bower packages can specify exports. They don’t have to. JQuery doesn’t. But we can configure them ourselves. Here’s what our bower.json looks like now:

{
  "name": "shiny-shiny",
  "version": "0.0.0",
  "homepage": "https://github.com/georgecodes/shiny-shiny",
  "authors": [
    "George McIntosh <george@georgecodes.com>"
  ],
  "license": "MIT",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "jquery": "1.9.1"
  },
    "exportsOverride": {
      "jquery": {
          "js": "jquery.js"
      }
    }
}

We’ve said “just give us the distro, thanks”.

So now we can introduce our grunt config.

module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        sass: {
            dist: {
                options: {
                    outputStyle: 'compressed'
                },
                files: {
                    'web-app/css/main.css': 'grails-app/assets/stylesheets/scss/main.scss'
                }
            }
        },

        watch: {
            grunt: { files: ['Gruntfile.js'] },

            sass: {
                files: 'grails-app/assets/stylesheets/scss/**/*.scss',
                tasks: ['sass']
            }
        },
        bower: {
            install: {
                options: {
                    cleanTargetDir: true,
                    cleanBowerDir: true,
                    targetDir: 'web-app/js/lib',
                    layout: function(type, component) {
                        return '';
                    }
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-sass');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-bower-task');

    grunt.registerTask('build', ['sass']);
    grunt.registerTask('bootstrap', ['bower:install']);
    grunt.registerTask('default', ['build','watch']);
}

We’ve added a bower task, and we’ve configured it to export the distribution to our grails app. That ‘layout’ function we provide is just a hack to flatten the file structure out a bit. By default we’d have a bunch of extra folders that we don’t really want. We just want the library.

Now, if we run

1
$ grunt bootstrap

we see that grunt uses Bower to grap JQuery, and then stick the resultant library in our grails app’s web-app dir. It also does a bit of cleaning up after itself, removing the bower_components directory.

Ok, so now we can just declare JQuery like any other resource. Let’s do that. First, free yourself from the chains of the JQuery plugin; delete it from BuildConfig.groovy. Now declare a jquery lib in ApplicationResources.groovy

modules = {

    jquery {
        resource url: 'js/lib/jquery.js'
    }

}

and put

<r:require module="jquery"/>

somewhere in your GSPs. You know the drill. Now, fire up the app, and you’re serving up your own, dependency-managed JQuery. Hooray! A bit of refactoring of the gruntfile. and we can delete grails-app/assets altogether.

module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        sass: {
            dist: {
                options: {
                    outputStyle: 'compressed'
                },
                files: {
                    'web-app/css/main.css': 'web-app/scss/main.scss'
                }
            }
        },

        watch: {
            grunt: { files: ['Gruntfile.js'] },

            sass: {
                files: 'web-app/scss/**/*.scss',
                tasks: ['sass']
            }
        },
        bower: {
            install: {
                options: {
                    cleanTargetDir: true,
                    cleanBowerDir: true,
                    targetDir: 'web-app/js/lib',
                    layout: function(type, component) {
                        return '';
                    }
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-sass');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-bower-task');

    grunt.registerTask('build', ['sass']);
    grunt.registerTask('bootstrap', ['bower:install']);
    grunt.registerTask('default', ['build','watch']);
}

So our current workflow is to use

1
$ grunt bootstrap

To fetch and avail JQuery to our app, and to just grails run-app to compile and serve Sassy scss. There are, of course, a number of ways of simplifying this, but that’s for another time. All that remains is to add a few things to our .gitignore, and we’re away. We don’t really want to check in any generated or otherwise managed code, y’see:

target
shiny-shiny.iml
node_modules
web-app/js/lib
web-app/css/**

Code for this post is, as always, on GitHub.

Comments