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
Hard to argue with all of the great things making their way into grunt et al. these days.
This is definitely the way to go forward.
[...] is a much nicer solution than using Exec like we did for the npm task, here’s the write-up: Calling GruntJS tasks from Gradle). Since the Grunt command is different depending on the operating system we’ll need to set [...]