Mobile Zone is brought to you in partnership with:

Kon Soulianidis is an engineer at Gradleware. Kon is also the leader of the Australia's largest JUG, the Melbourne Java & JVM Users Group. Kon has posted 1 posts at DZone. You can read more from them at their website. View Full User Profile

JavaScript Webapps with Gradle

03.24.2014
| 9155 views |
  • submit to reddit

 

Duke_the_Ripper IAP4377

Gradle is a build tool on the JVM platform that’s been gaining prominence over the last few years. Gradle’s reach doesn’t stop with JVM languages though.  The most recent releases, 1.10 and 1.11, have improved support for compiling native code (C / C++ / Objective C) and introduced support for the .NET platform. For Android projects, Google have made Gradle the de facto build tool. It’s getting a name for its flexibility, so when the opportunity came to build a single page Javascript webapp we decided to put the latest addition to the polyglot build tool arsenal through its paces.

In this post I’m going to look at why we chose Gradle over a native JS build tool, provide a quick tour of the JS/CSS plugins, look at the embedded Jetty webserver plugin, discuss testing your code in CI, and a variety of other tips and gotchyas found in our experience during the project.

Why not {{JavaScript_build_tool_flavour_of_the_month}}?

The momentum around JavaScript development is growing rapidly.  So rapidly in fact that it feels like the book 1984 where one day your nation is fighting a war with one country, then all of a sudden wakes up, and it’s announced it has pledged an allegiance to a former enemy; your former ally becoming the new enemy.

Twitter and the blogosphere recently became alive with talk about Gulp, a new JS build tool. This blog post ‘And just like that, Grunt and RequireJS are out – its all about Gulp and Browsify now’ and this Twitter post felt like the shifting of alliances in 1984. This is something you’ll have to factor into your JS build tool decision making. It also means however there is still room for contenders in this space.Grunt isn't the new hotness

Having the same build tool for both client side and server side builds helps developers understand the project better. Our team had Groovy and Gradle exposure already and whilst we were happy to try a JS build tool, it would be nice to consider the same tools for both aspects of the project.

A tool that works with your existing infrastructure is also important. The straw that broke the camel’s back was our client’s firewall. Firewalls are part and parcel of something we have to work around as consultants. Sometimes you win, sometimes you lose. Simply put, our client’s firewall made npm cry as the https proxy had some confusing message about SSL certificate algorithms. After trying to work around this for a while, I noticed we had a perfectly fine build tool that already worked fine with Java and our proxy. The question was asked: Are there any JavaScript plugins for Gradle?

Reinventing the wheel

I heard at a JavaScript BOF at JavaOne someone remark that ‘JavaScript had been rapidly climbing the tree of software development, reinventing tools that had already existed on established platforms, then falling off the tree taking each branch along with it on the way down.’ Although tongue-in-cheek, it did capture the frustrations of tools already built that were largely ignored, or unknown by the JavaScript community.

From what I’ve seen so far, the other popular JVM build tool, Maven, has a large number of plugins to aid in the preparation of JavaScript files.  Most of these use the Rhino JavaScript Engine that comes with Java in order to execute these utilities on the JVM platform where the build and testing is performed. The eco-system of running JavaScript utilities out of Rhino with ScriptEngineManager is well established.  Additionally there are a lot of other tools written in Java to validate and compress JavaScript files – the Google Closure Compiler is one that is used by the Gradle JS and CSS plugin. It appears to have been relatively straight forward for plugin devs to repackage all these tools as Gradle plugins that kick off the same scripts from within.  Where there isn’t a Java port or Jar, Gradle plugins or tasks can always launch JS tools from the command line.

Kicking the tires

So just what can Gradle with the available JS plugins do? Here’s a whirlwind tour of the available features.

The Gradle JS and CSS plugins

The Gradle JS plugin is a third party Gradle plugin written by Eric Wendelin. It can do file combination, minification, Gzip, JSHint and more. And although we didn’t need it for this project, you’ll be pleased to know it has require.js support as well. The Gradle CSS Plugin also written by Eric Wendelin, again supports combining and minification plus supports less compilation and csslint.  To add these plugins to your build and associated tasks, all you have to do is add this to your build.gradle

apply plugin: 'js'
apply plugin: 'css'
 
// define the dependencies gradle buildscript will use to build
// the app (not the app itself)
buildscript {
   repositories {
      mavenLocal()
      mavenCentral()
   }
 
   dependencies {
      classpath 'com.eriwen:gradle-css-plugin:1.8.0'
      classpath 'com.eriwen:gradle-js-plugin:1.9.0'
   }
}

You can then configure the tasks the plugin provides, combineJs, minifyJs, gzipJs as follows:

combineJs {
    // pull together the source from string & file lists 
    //  eg. def core = ["$webAppDirName/init.js",...]
    source = core + application + devmode + bigquery +
       javascript.source.externalLibs.js.files +
       dfpFilters + chartdef + uxFilters 
    // show the resolved files when gradle is run with -d
    source.each{ logger.debug ("$it") }
    dest = file("${buildDir}/all.js")
} 
minifyJs {
    source = combineJs
    dest = file("${buildDir}/all.min.js")
    sourceMap = file("${buildDir}/all.sourcemap.json")
    closure {
       warningLevel = 'QUIET'
       compilerOptions.defineReplacements = ['MY_DBUG_FLAG':false]
    }
} 
gzipJs {
    source = minifyJs.dest
    dest = file("${buildDir}/all.min.js.gz")
}

combineJs has a source property which takes a collection. We split our app into groups of JavaScript files and combine them with a list of files from a Gradle javascript sourceset called externLibs that references bootstrap and jquery in a separate dir in our project.  The combine is done in the order you specify when specified in an ArrayList. For Gradle aficionados, this is why I’m not using sourcesets for our files – see here. The other reason is that all our client JS was in the same folder so lists were the most concise way to declare these file groups.

You can see we can also debug the list to make sure we’ve pulled in the files we think we’ve asked for by using standard groovy code to iterate through the source collection, making use of the logger that is injected into the build file by Gradle.

One of the neatest things about Gradle is that each task has a set of Input files or dirs, plus output files.  Gradle can do some very intelligent introspection into whether or not a file has been modified and what dependent tasks need to be rerun.  Spending a little effort to specify these properly means that your build won’t have to do unnecessary stuff repeatedly.  The minifyJs task takes advantage of this.  We can then declare the input for its source property as the dest outputfrom the previous task.  Here we set source to combineJs and gradle figures out we mean the output of that task (we could have also said combineJs.dest).  The cool thing about this is that Gradle will be lazy. If it notices that the output on the combineJs hasn’t changed, thus the input on minifyJs hasn’t changed, it will skip running that task.  BTW, you can always force Gradle to run each task with the --rerun-tasks command line parameter.

To help things along we can define the dependency chain on these tasks

tasks.minifyJs.dependsOn tasks.combineJs
tasks.gzipJs.dependsOn tasks.minifyJs

and run gzipJs each time being confident it will only run the earlier tasks if their task inputs have changed.  I’m not sure why the dependency chain isn’t configured by default in the JS plugin – perhaps it could be a reasonable enhancement for a future release?

I mentioned that minifyJs uses the Google Closure Compiler under the hood and so I wanted to show our use of configuring it with closure config block, in particular the compilerOptions.defineReplacements option which wasn’t documented in the JS plugins docs. In our JS code we define a constant to indicate some developer mode functionality like so:

// config.js
/**
 * Flag to indicate console and extra logging throughout the app
 * @define {boolean} allow the value of this bool to be
 *           overwritten at closure compiler / minification time
 * @const
 * @type {boolean}
 */
var MY_DBUG_FLAG = true;

Then elsewhere throughout our code base, we have code like this which modifies the DOM and adds some helpful info about the applications state to the UI.

if (MY_DBUG_FLAG) {
    $('.dashboard').append(
        '<p>Here's some dev info you wouldn't normally see</p>'
    );
}

defineReplacements allows the Google Closure compiler to redefine the MY_DBUG_FLAG constant at build time to false, and then through its optimisation, remove the if (MY_DBUG_FLAG) blocks from the resultant all.min.js entirely (since we’ve forced MY_DBUG_FLAG to false).

There are plenty of other configuration options for the compiler but it’s not immediately apparent where these are set.  Take a look at CompilerOptions.java in the Google Closure compiler source to see the properties it takes.

Published at DZone with permission of its author, Kon Soulianidis. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)