Setting up Maven to use Grunt/NodeJS
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.
<br> <plugin><br> <groupId>com.github.eirslett</groupId><br> <artifactId>frontend-maven-plugin</artifactId><br> <version>0.0.16</version><br> <!-- executions go here --><br> </plugin><br>
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.
<br> <execution><br> <id>install node and npm</id><br> <phase>generate-resources</phase><br> <goals><br> <goal>install-node-and-npm</goal><br> </goals><br> <configuration><br> <nodeVersion>v0.10.18</nodeVersion><br> <npmVersion>1.3.8</npmVersion><br> </configuration><br> </execution><br>
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.
<br> {<br> "name":"frontend-tools",<br> "version":"0.0.1",<br> "dependencies": {<br> "grunt": "~0.4.5",<br> "grunt-cli": "~0.1.13",<br> "grunt-contrib-jshint":"~0.10.0"<br> },<br> "devDependencies": {}<br> }<br>
With the package.json in place, the ‘npm install’ execution can be added to the plugin executions.
<br> <execution><br> <id>npm install</id><br> <phase>generate-resources</phase><br> <goals><br> <goal>npm</goal><br> </goals><br> <configuration><br> <arguments>install</arguments><br> </configuration><br> </execution><br>
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.
<br> <execution><br> <id>grunt build</id><br> <phase>generate-resources</phase><br> <goals><br> <goal>grunt</goal><br> </goals><br> <configuration><br> <arguments>--verbose</arguments><br> </configuration><br> </execution><br>
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.
<br> module.exports = function( grunt ){</p> <p> // tell grunt to load jshint task plugin.<br> grunt.loadNpmTasks('grunt-contrib-jshint');</p> <p> // configure tasks<br> grunt.initConfig({<br> jshint: {<br> files: [<br> 'GruntFile.js',<br> 'src/main/resources/javascript/**/*.js',<br> 'src/test/javascript/**/*.js'<br> ],<br> options: {<br> ignores: [<br> 'src/main/resources/javascript/lib/**/*.js'<br> ]<br> }<br> }<br> // more plugin configs go here.<br> });</p> <p> grunt.registerTask('default',['jshint']);</p> <p>};<br>
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.
<br> alert("Hello world!") // missing semicolon<br>
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:
<br> alert("Hello world!"); // added semicolon<br>
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:
<br> <execution><br> <id>npm set proxy</id><br> <phase>generate-resources</phase><br> <goals><br> <goal>npm</goal><br> </goals><br> <configuration><br> <arguments>config set proxy http://YOUR_PROXY:PORT</arguments><br> </configuration><br> </execution></p> <p><execution><br> <id>npm set HTTPS proxy</id><br> <phase>generate-resources</phase><br> <goals><br> <goal>npm</goal><br> </goals><br> <configuration><br> <arguments>config set https-proxy https://YOUR_PROXY:PORT</arguments><br> </configuration><br> </execution><br>