Calling GruntJS Tasks From Gradle

| Comments

Gradle is a great build tool with a large community for developing JVM-based applications, but one area that it seems to be lacking strong support is in front-end tooling. The Node.js community’s strength is front-end tooling with a number of very nice build tools including Grunt, Yeoman and Brunch.

There are a couple of Gradle plugins that people have created around JavaScript and CSS processing, but even the authors of those plugins seem to have punted and moved to node.js-based tools for front-end work.

I’m using Grunt on my latest project to help out with packaging, minification, and concatenating files (all through RequireJS’s r.js optimization), linting (through JSHint) and css pre-processing (via LESS).

I wanted a way for our build process to be able to assemble our .war files in a single step so I needed to figure out how to weld these two tools together.

The quick and dirty way of doing it would just be to either hard code some “grunt”.execute().text lines, or to have each grunt task be an Exec task:

task requirejs(type: Exec) {
    commandLine 'grunt', 'requirejs'
}

One other limitation that we had was that some of our developers (and our build machine slaves) are on Windows boxes, and other developers are using OSX. On Windows, the grunt command is spelled grunt.cmd and once I started having to repeat OS checks everywhere things started to feel less DRY and more hacky.

After a little research, I was able to figure out how to create a custom Gradle Exec subclass that keeps things clean:

import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.api.tasks.Exec

...

task requirejs(type: GruntTask) {
    gruntArgs = "requirejs"
}

task jslint(type: GruntTask) {
    gruntArgs = "lint"
}

...

class GruntTask extends Exec {
    private String gruntExecutable = Os.isFamily(Os.FAMILY_WINDOWS) ? "grunt.cmd" : "grunt"
    private String switches = "--no-color"

    String gruntArgs = "" 

    public GruntTask() {
        super()
        this.setExecutable(gruntExecutable)
    }

    public void setGruntArgs(String gruntArgs) {
        this.args = "$switches $gruntArgs".trim().split(" ") as List
    }
}

You can either put the GruntTask class directly in your build.gradle file (where it won’t have a package) or else in a directory under buildSrc/src/main/groovy where it should automatically be included in your build. It’s probably better for organization purposes to have it in buildSrc, but there seems to be a performance impact to gradle needing to check that directory is current all the time.

With the GruntTask class in place, you can treat your Grunt tasks just like Gradle tasks, including making things like your war task depend on requirejs running first, or you can pass your tasks in explicitly:

gradle clean lint requirejs war

Comments