Making Maven Grunt.

July 23, 2013

Getting Grunt, Yeoman and Bower into your Maven workflow

Introduction

Developers working in agencies often tell us that they want to be able to use modern front-end tooling in their workflow. One set of options this manifests as is using Grunt for their build tasks (instead of Ant, bash or Gradle), Bower for dependency management (instead of Maven or Ivy) and Yeoman for application scaffolding. For personal projects, these tools are straightforward to get setup.

Note: If you are new to this tooling chain, Grunt is a JavaScript task runner which you can think of as Ant for JavaScript. It has a vast ecosystem of predefined tasks, some of which are maintained by the core team and many contributed by the community. There are a number of guides available to help you get started. There's a beginner's guide to Grunt, Building a JS library with Grunt.js and Building apps with the Yeoman workflow, all of which are solid recent introductions for beginners.

Complexity however arises when you're working in a team where some developers focus on the client-side and others on the back-end - it's a situation that is quite commonplace. More times than not,it's the

back-end team who will define the build process and end up having to wrangle your front-end code into there. Was Maven chosen for this purpose? A lot of the times the answer to this question is sadly "yes".

Whilst there are many plugin options for JavaScript processing in the Maven world, these plugins can be a real chore to create, maintain and update. In addition, they often have outdated dependencies - meaning you need to monkey-patch them to work and developers - especially those not coming from a Java background - dislike it.

Note: If you're using Gradle instead of Maven, Ted Naleid has a guide for calling Grunt tasks from Gradle that's got you covered.

Tooling options

Our biggest piece of feedback from developers stuck in this situation is:

"We don't know how to integrate Grunt tooling into our Maven workflow. Even after setting up Node on our dev. box, there's no documentation about using Maven with Grunt and everyone seems to want it but there are no blessed solutions out there".

Thinking about this problem at a high-level there are a few options available:

1) Consider your backend and front-end code as completely separate entities. Your backend should just be an API and you can maintain your front-end as a separate client app that consumes the data it provides as a service. In this scenario using completely different tools shouldn't be too much of a problem. Your front-end engineers can use Grunt/Yeoman/Bower and your back-end team can continue using Maven.

2) Ignore modern tooling altogether. Rather than considering both projects as independent pieces, find or write Maven plugins for building your front and back-end, running tests and managing dependencies as needed. Options include the Rhino based JAWR - which hasn't been updated in forever and Wro4J - which has an extensive plugin list but is slow compared to Node tooling. The problem with this solution is that whilst it works, you just won't have the task flexibility as you get with Grunt's task ecosystem.

3) Use the Maven-exec plugin to call out to Grunt from your existing build process so it can build your front-end code, run tests and so on. You could alternatively use the ant-run plugin together with the ant-exec task (thanks to this SO thread for the tip).

If I was starting work on a completely new web application, I'd always go for option 1). It's the most clean and because your backend is being treated as an API / data service other apps your company developers could easily use it without having to worry about it being coupled to just one project.

For most other cases, option 3) will allow you to get Grunt into your Maven workflow.

Making Maven Grunt

Speaking of options, here are a few code snippets that will help you get Grunt into your Maven workflow. For option 3), Maven user implemented an ant-run plugin setup for using Grunt that looked a little like this:

<plugin>
  <artifactId>maven-antrun-plugin</artifactId>
  <version>1.7</version>
  <executions>
      <execution>
<phase>generate-sources</phase>
<configuration>
    <target name="building">
        <echo>
<!--  NPM INSTALL  -->
        </echo>
        <exec executable="cmd" dir="${project.basedir}"
        osfamily="windows" failonerror="true">
            <arg line="/c npm config set color false"/>
        </exec>
        <exec executable="bash" dir="${project.basedir}"
        osfamily="unix" failonerror="true">
            <arg line="npm config set color false"/>
        </exec>
        <exec executable="cmd" dir="${project.basedir}"
        osfamily="windows" failonerror="true">
            <arg line="/c npm install"/>
        </exec>
        <exec executable="bash" dir="${project.basedir}"
        osfamily="unix" failonerror="true">
            <arg line="npm install"/>
        </exec>
        <echo>
<!-- GRUNT  -->
        </echo>
        <exec executable="cmd" dir="${project.basedir}"
        osfamily="windows" resultproperty="cmdresult">
            <arg line="/c grunt --no-color >
            grunt.status "/>
        </exec>
        <exec executable="bash" dir="${project.basedir}"
        osfamily="unix" resultproperty="cmdresult">
            <arg line="grunt --no-color > grunt.status"/>
        </exec>
        <loadfile property="grunt.status"
        srcFile="grunt.status"/>
        <echo>${grunt.status}</echo>
        <delete file="grunt.status" quiet="true"/>
        <condition property="cmdsuccess">
            <equals arg1="${cmdresult}" arg2="0"/>
        </condition>
        <fail unless="cmdsuccess"/>
    </target>
</configuration>
          <goals>
              <goal>run</goal>
          </goals>
      </execution>
  </executions>
</plugin>

For the Maven-exec sample, the following should help you get setup:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <version>1.2.1</version>
  <executions>
    <execution>
      <phase>prepare-package</phase>
      <goals>
        <goal>exec</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <executable>grunt</executable>
  </configuration>
</plugin>

You can customise your Maven-exec setup to run a shell script that calls out to any custom Grunt build profiles (e.g grunt build, grunt build:prod --force, grunt build:dev --force) and so on. As mentioned earlier, your only real requirement is that Node needs to be available on your box in order for Grunt (and other tools) to be run from your Maven setup. That's it.

Handling WAR Files

Okay, so we can now integrate Grunt into our Maven workflow but that isn't the complete story. You still ultimately need to be able to produce a WAR file - a JAR file used to distribute a collection of JavaServer Pages, servlets, static Web pages (HTML/CSS/JS) and other resources that together constitute a Web application in a Java driven web app.

For reference, GitHub user ahabra put together a boilerplate for Maven projects wishing to use Grunt which includes:

  1. Maven project which produces war file
  2. Maven dependencies for: junit, commons-lang3, guava-13
  3. The web app directory includes a JavaScript directory for your scripts (/js)
  4. Third-party JS files are in js/vendor, include: jquery, underscore.js, namespace.js, tstring.js
  5. Jasmine specs in test/js/jasmine-specs
  6. Gruntfile.js configured to lint all the js code and run jasmine specs.

Not to shabby. A prescribed Maven + Grunt workflow is also where some users have found the Yeoman Maven plugin useful.

Like Maven-exec, it helps you run grunt from your Maven build but also ensures that Bower gets all of your needed dependencies too. With just a small amount of configuration you can use all of the modern tools we introduced earlier without too much extra work.

Using the plugin is fairly easy. First, declare the plugin:

<plugin>
    <groupId>com.github.trecloux</groupId>
    <artifactId>yeoman-maven-plugin</artifactId>
    <version>0.1</version>
    <executions>
        <execution>
            <goals>
                <goal>build</goal>
            </goals>
        </execution>
    </executions>
</plugin>

You'll then want to add the yeoman dist directory to your WAR file.

<plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.3</version>
            <configuration>
        <webResources>
            <resource>
                <directory>yo/dist</directory>
            </resource>
        </webResources>
    </configuration>
</plugin>

And finally configure the clean plugin in order to delete generated directories.

<plugin>
    <artifactId>maven-clean-plugin</artifactId>
    <version>2.5</version>
    <configuration>
        <filesets>
            <fileset>
                <directory>yo/dist</directory>
            </fileset>
            <fileset>
                <directory>yo/.tmp</directory>
            </fileset>
            <fileset>
                <directory>yo/app/components</directory>
            </fileset>
            <fileset>
              <directory>yo/node_modules</directory>
            </fileset>
        </filesets>
    </configuration>
</plugin>

Note: If you happen to use Jetty, the the Java HTTP web server, Patrick Grimard has a good write-up about setting it up with the Yeoman Maven plugin that you might find useful.

Dependency management

Next, let's talk a little more about dependency management in your app using Maven. When working on an app with a Java backend and a HTML/JS/CSS front-end Bower can actually help keep your project more maintainable.

Instead of having to check-in your front-end dependencies into the src/main/webapp directory, your bower.json file can be checked into your repository instead. Once again using the Maven exec plugin, Bower can be run as a part of your Maven build. This avoids the need to check-in your front-end.

From xebia:

<plugin>
   <groupId>org.codehaus.mojo</groupId>
   <artifactId>exec-maven-plugin</artifactId>
   <executions>
     <execution>
     <phase>generate-sources</phase>
     <goals>
      <goal>exec</goal>
     </goals>
     </execution>
   </executions>
   <configuration>
     <executable>bower</executable>
     <arguments>
      <argument>install</argument>
     </arguments>
     <workingDirectory>${basedir}/src/main/webapp
</workingDirectory>
   </configuration>
 </plugin>

Middleware

Another question that might arise is how your back-end web server, such as Tomcat might fit in as middleware.

When attempting to make remote XHR calls to your web server (which may be running on a different port) you may find them failing as your browser will refuse to run an XHR to a different origin. One solution to this is adding a proxy within your local server to handle same-origin restriction. The grunt-connect-proxy provides a HTTP proxy as middleware for the grunt-contrib-connect plugin.

After installing the module, your Grunt server will proxy requests to your back-end as follows:

var proxySnippet
  = require('grunt-connect-proxy/lib/utils').proxyRequest ;

// Modify the connect configuration task:
// add proxies section and insert 'proxySnippet'
// in the middleware
   connect : {
     proxies : [
       {
         context : '/rest' ,
         host : 'localhost' ,
         Port : 8080 ,
         https : false ,
         changeOrigin : false
       }
     ]
     options : {
       port : 9000 ,
       hostname : 'localhost'
     },

     livereload : {
       options : {
         middleware : function ( connect ) {
           return [
             proxySnippet ,
             lrSnippet ,
             mountFolder ( connect , . 'tmp' )
             mountFolder ( connect , yeomanConfig . app )
           ];
         }
       }
     }
    }
  }

// Modifify the server task to coincide with
// the configureProxies step
 grunt.registerTask ('server' , [
   'clean: server' ,
   'coffee dist' ,
   'compass: server' ,
   'configureProxies' ,
   'livereload-start',
   'connect: livereload' ,
   'open' ,
   'watch'
 ]);

Conclusions

Whilst I'm by no means a Maven expert, I hope you've found some of the tips in this write-up useful.

As a community, I think we can do better. Creating better boilerplates, Yeoman generators and docs for the Maven + Grunt workflow would greatly help a number of developers. We need engineers with experience in these tools to help us work on open-source solutions to these problems. It's necessary for modern tooling to really have an impact in the real world.

That said, If you're working on a project where the front-end and back-end are somewhat coupled, using Maven as your primary build system with binaries like Grunt and Bower called out as needed is completely feasible. For fresh projects, keeping these two pieces distinctly separate comes with the flexibility to use whatever tooling chain you want, including just using Grunt for your front-end.

Further reading