Trifork Blog

Axon Framework, DDD, Microservices

Setting up Maven to use Grunt/NodeJS

October 7th, 2014 by
|

grunt-logo For one of our projects we wanted to automate javascript concatenation/minification/tests and incorporate it into our maven build. While there are a number of maven plugins for those tasks, I’ve found that depending on another technology offered so much more and basically ended up integrating Grunt into our maven build. Grunt is a task runner which runs in node.js and it along with its plugins (tasks) are distributed with NPM. One could compare it with Maven + Ant with one big advantage for frontenders, it’s all javascript driven. This advantage means there’s a wider scenery of tooling catered specifically (but not exclusively) for frontend development and makes it quite delightful to find and use tools. It will feel weird to integrate a dependency manager and task runner into another dependency manager and task runner, but it actually makes sense from a polyglot point of view. Don’t worry though, we will be using Maven to add Grunt to the build.

Setup

For our setup we’re going to setup a simple webapp module with the following folder structure and files.

module
 + src
 .+ main
 ..+ resources
 ...+ javascript
 ....+ lib
 ....- HelloWorld.js
 ..+ webapp
 .+ test
 ..+ javascript
 - pom.xml
 - package.json
 - GruntFile.js

Enter the frontend maven plugin

The frontend maven plugin by Eirik Sletteberg adds the node.js binary to your project/module (no install required) and delegates commands to the binary. This means that you do not need to install node.js on your development machines or build servers. And if you want to use the binaries on the commandline, Eirik also offers some helper scripts to do just that, or just run your own local version alongside as I do.

<plugin>
   <groupId>com.github.eirslett</groupId>
   <artifactId>frontend-maven-plugin</artifactId>
   <version>0.0.16</version>
   <!-- executions go here -->
</plugin>

The following execution imports node.js and npm binaries to your project/module. Remember to add the ‘node’ folder to your scm ignore as downloaded binaries are OS dependant. It’s also safe to have node.js and npm installed on your machine, the frontend maven plugin will still use the downloaded binaries.

<execution>
   <id>install node and npm</id>
   <phase>generate-resources</phase>
   <goals>
       <goal>install-node-and-npm</goal>
   </goals>
   <configuration>
       <nodeVersion>v0.10.18</nodeVersion>
       <npmVersion>1.3.8</npmVersion>
   </configuration>
</execution>

Next create a package.json on the same level as the pom. The package.json has pretty much the same responsibility for npm as the pom has for maven. There will also be a GruntFile.js on the same level, but we’ll get there later on. The package.json will contain a description of your project and it’s dependencies. Our first task will be to lint our javascript, so we add the grunt-contrib-jshint plugin.

{
   "name":"frontend-tools",
   "version":"0.0.1",
   "dependencies": {
      "grunt": "~0.4.5",
      "grunt-cli": "~0.1.13",
      "grunt-contrib-jshint":"~0.10.0"
   },
   "devDependencies": {}
}

With the package.json in place, the ‘npm install’ execution can be added to the plugin executions.

<execution>
    <id>npm install</id>
    <phase>generate-resources</phase>
    <goals>
        <goal>npm</goal>
    </goals>
    <configuration>
        <arguments>install</arguments>
    </configuration>
</execution>

Now npm will install dependencies in the ‘node_modules’ when you run the maven build, in this case the grunt-contrib-jshint plugin (and it’s dependencies). You might want to add ‘node_modules’ to your scm ignore as well, however there’s a little caveat about npm and continuous integration, which I will touch later on. And the last fundamental execution will be to kickstart the grunt task runner. I usually add the –verbose flag so I can see more whats happening on the console when running the maven build.

<execution>
    <id>grunt build</id>
    <phase>generate-resources</phase>
    <goals>
        <goal>grunt</goal>
    </goals>
    <configuration>
        <arguments>--verbose</arguments>
    </configuration>
</execution>

Enter Grunt

When you run the maven build now, you will get a grunt error, we still need to add the actual GruntFile.js which tells grunt what to do.

module.exports = function( grunt ){

   // tell grunt to load jshint task plugin.
   grunt.loadNpmTasks('grunt-contrib-jshint');

   // configure tasks
   grunt.initConfig({
      jshint: {
          files: [
             'GruntFile.js',
             'src/main/resources/javascript/**/*.js',
             'src/test/javascript/**/*.js'
          ],
          options: {
             ignores: [
                'src/main/resources/javascript/lib/**/*.js'
             ]
          }
      }
      // more plugin configs go here.
   });

   grunt.registerTask('default',['jshint']);

};

In the above grunt configuration, we define the jshint task, where we lint the javascript code of the GruntFile itself and all scripts in the ‘src/main/resources/javascript’ and ‘src/test/javascript’ folders. Since 3rd party script “should” not require quality checks, the option to ignore the ‘lib’ folder is added which will contain all 3rd party scripts. More plugin configuration can be added to the ‘options’ section, check out jshint documentation for more options. Finally jshint is added to the default task with ‘grunt.registerTask’, this means you can run ‘grunt’ on the commandline (when you have a local nodejs installed or are using the helper scripts) and the ‘jshint’ task will be executed. Now write some javascript that should not pass linting in HelloWorld.js.

alert("Hello world!") // missing semicolon

Run ‘maven package’ and the build should fail with the following output:

[INFO] Running "jshint:files" (jshint) task
[INFO] Linting src/main/resources/javascript/components/helloWorld.js ...ERROR
[INFO] [L1:C22] W033: Missing semicolon.
[INFO] alert("hello world!") 

Fix the javascript:

alert("Hello world!"); // added semicolon

And run the maven build anew to get a succesful result.

[INFO] Running "jshint:files" (jshint) task
[INFO] >> 2 files lint free.
[INFO] 
[INFO] Done, without errors.

WHY?

Just linting javascript isn’t reason enough to use grunt in your maven builds, however once you want to do more like running javascript integration tests with karma/jasmine and “compiling” AMD javascript modules, it’s a lot easier to do that with Grunt than searching high and low for the right maven plugins without ending up with ant builds. For example, here’s a list of grunt plugins we’re leveraging in one of our projects:
grunt.loadNpmTasks(‘grunt-contrib-jshint’);
grunt.loadNpmTasks(‘grunt-contrib-requirejs’);
grunt.loadNpmTasks(‘grunt-karma’);
grunt.loadNpmTasks(‘grunt-contrib-watch’);
grunt.loadNpmTasks(‘grunt-contrib-compass’);
grunt.loadNpmTasks(‘grunt-contrib-copy’);
grunt.loadNpmTasks(‘grunt-contrib-clean’);
grunt.loadNpmTasks(‘grunt-karma-sonar’);
grunt.loadNpmTasks(‘grunt-jsdoc’);
grunt.loadNpmTasks(‘grunt-docco’);
Automated concatenation/minification, documentation, unit and integration tests taken from the frontend developer workflow and integrated into a maven build. I think this is rather neat.

Caveats

The NPM registry is not stable enough for continuous integration and many advice to store your node_modules into your SCM. There are pros and cons to that solution. An alternative is to configure a caching proxy for NPM and you can do that using the frontend-maven-plugin:

<execution>
    <id>npm set proxy</id>
    <phase>generate-resources</phase>
    <goals>
        <goal>npm</goal>
    </goals>
    <configuration>
        <arguments>config set proxy http://YOUR_PROXY:PORT</arguments>
    </configuration>
</execution>

<execution>
    <id>npm set HTTPS proxy</id>
    <phase>generate-resources</phase>
    <goals>
        <goal>npm</goal>
    </goals>
    <configuration>
        <arguments>config set https-proxy https://YOUR_PROXY:PORT</arguments>
    </configuration>
</execution>

9 Responses

  1. October 14, 2014 at 14:48 by Adam Molnár

    Thanks Ben for sharing this.
    Finally I managed to integrate Grunt into into Maven using the frontend-maven-plugin. Now I’m standing in front of the next hurdle: to get sass compiled via grunt-contrib-compass plugin without necessarily having installed ruby locally. You have listed this Grunt plugin under the chapter WHY? Did you get it run? Do you need an additional sass or compass maven-plugin?

  2. November 19, 2014 at 12:19 by Nick

    I’ve done described steps, but nothing works 🙁

  3. November 26, 2014 at 10:28 by Ben Gerrissen

    Hi Adam,

    I only use grunt plugins for SASS when developing with a local nodejs/sass/compass setup, so in our GruntFile I’ve configured a ‘watch’ plugin to update files on change. For the maven build however (especially for jenkins/hudson) we use the sass-maven-plugin https://github.com/Jasig/sass-maven-plugin which uses jruby to run the sass/compass ruby gems (no dependency on locally installed ruby/sass/compass).

  4. December 16, 2014 at 17:26 by Keith

    Thanks, great article. One question – how do you get the files out of the grunt compile dir and back into the WAR?

  5. February 26, 2015 at 11:13 by Vivek

    Great article Thanks

  6. March 30, 2015 at 17:57 by Stephane

    Having directions on using this plugin on an existing AngularJS application would help.

  7. May 8, 2015 at 09:02 by Michael Bushe

    Keith, you can create a war by having your packaging be on type pom, and using the maven-assembly-plugin with a assembly descriptor with format “war”.

  8. September 1, 2015 at 10:55 by borgulas

    Nice tut. Just started using Maven and need to use it icw Gulp for an AngularJS project.Can convert what I learned here to applying it with Gulp!

  9. September 16, 2015 at 13:29 by Fogathajtó Döntő

    Gulp vs Grunt…hm, one of the newest option for a flamewar on the net

    – a Gulp fan:)