It is a quite common scenario nowadays to have a mixed stack of frontend and backend technologies. One of my favorite stacks the last years was or is the combination of a REST-based backend together with a SPA (single page architecture) framework as the frontend technology plus user interface.
My favorite one is still AngularJS here. On the backend, I really love working with all kinds of the modules the Java Spring Framework provides instead of other JEE frameworks or even Node.JS. The stability and robustness makes it very reliable for an enterprise architecture on the backend to last the next 5-10 years.
By the way – they have an awesome blog series on “how to combine AngularJS and Spring” since January 2015 – have a look at it (as of 2nd of February 2015):
- Part I – Spring and Angular JS: A Secure Single Page Application
- Part II – The Login Page: Angular JS and Spring Security Part II
- Part III – The Resource Server: Angular JS and Spring Security Part III
- Part IV – The API Gateway Pattern: Angular JS and Spring Security Part IV
- Part V – SSO with OAuth2: Angular JS and Spring Security Part V
So if you really got that deep into this stack – pretty cool and fine and you’re already very far now :-).
But I wanted to talk about using gradle and grunt as the build tooling infrastructure. Grab your gradle wrapper / gradle base installation from their homepage at http://gradle.org/.
Then, setup a base gradle.build script in your project root or setup a base gradle project within your favorite IDE or download a template. Please include the two repositories for getting the plugins plus define them as dependencies in the buildscript section. Do not mix it up with the project dependencies as this is only for the buildprocess.
buildscript { repositories { mavenCentral() jcenter() } dependencies { classpath 'com.moowork.gradle:gradle-grunt-plugin:0.6' classpath 'com.moowork.gradle:gradle-node-plugin:0.8' } } ...
After including these two plugins into the build process, activate and configure them
apply plugin: 'com.moowork.grunt' apply plugin: 'com.moowork.node' node { // Version of node to use. version = '0.10.33' // Base URL for fetching node distributions (change or activate it, when using a private mirror). // distBaseUrl = 'http://foo.bar.com/dist/nodejs' // Enabled the automatic download. False is the default. This always downloads a local node env into the workspace! download = true // define a local workdir, where the downloaded Node.JS is put into. workDir = file("${project.buildDir}/nodejs") }
After configuring and activating these plugins, the build job will run predefined tasks if you integrate them in your build script.
It is also important to have a consistent build order as the grunt build will require a working node environment. Therefore, take care that you’re making the grunt build steps
dependent on the node setup and npm install tasks.
This can be accomplished with some dependsOn ordering. By looking at the modules homepage, you’ll see that you can use either syntax for the task calls.
Just for fun – I’ve included both in this example:
grunt_build.dependsOn(npm_cache_clean) grunt_build.dependsOn 'npmInstall' grunt_build.dependsOn 'installGrunt'
At the end, this will call
– npmSetup
– npmCacheClean
– npmInstall
– install a grunt environment “locally”
– trigger “grunt build”
Now, we will have an output in the build directory for further usage or we can also include it in another build step like building a war for a java environment. For this example, just define
war.dependsOn(grunt_build)
in your build script.
It is important to know – especially with respect to automated build environments like Jenkins, that everything is installed and deployed locally in your workspace. This means: The build script takes care of setting up and installing all necessary build tools withing the workspace. Pretty cool, eh?
But… careful! As of now, version 0.8 of the gradle-node-plugin has some issues in specific environments. I’ve created a github PR – this might be interesting if you’re building on a windows environment in an enterprise area. Sometimes, company admins tend to not know some consequences of standard-changes… therefore if you’ve got an environment variable named “Path” instead of “PATH” – the build script will fail at some point in time within specific node modules! See https://github.com/srs/gradle-node-plugin/pull/44 for more patch details as long as it is not merged… this bug kept me awake for hours before finding the simple reason… *sigh*
It will – only – come up if you don’t have a local node installation on the worker environment for the build.
Okay – in short, that’s it for the gradle part.
Let’s move over to the Javascript ecosystem and an example Gruntscript that could minify and uglify your AngularJS app and provides a consistent PhantomJS testing environment as the base for more complicated setups (side note: PhantomJS makes use of postinstall – scripts and calls “node install.js” – THIS was the main reason why I had a hard time finding the reason for breaking the build process – it was simply not finding a node interpreter in the PATH variable as the surrounding gradle-node-plugin set it to the wrong variable name…).
To have everything in place, have a package.json setup:
... "devDependencies": { "grunt": "~0.4.2", "grunt-cli": "~0.1.13", "grunt-closure-tools": "~0.9.2", "grunt-contrib-clean": "^0.6.0", "grunt-contrib-concat": "~0.3.x", "grunt-contrib-copy": "^0.5.0", "grunt-contrib-cssmin": "^0.10.0", "grunt-contrib-jshint": "~0.10.x", "grunt-contrib-uglify": "~0.3.x", "grunt-filerev": "^0.2.1", "grunt-ng-annotate": "^0.4.0", "grunt-usemin": "^2.3.0", "grunt-usemin-uglifynew": "0.0.3", "phantomjs": "1.9.13", "grunt-contrib-jasmine": "^0.8.0", "karma": "~0.12.21", "karma-chrome-launcher": "^0.1.7", "karma-jasmine": "~0.3.1", "jasmine-core": "~2.1" }, "engines": { "node": ">0.10.28" }, ...
There might be some modules, that are not used nor referenced in this example, but I just wanted to provide a complete configuration… 🙂 So if you need assistance in usemin / ng-annotate etc. – I could setup a second blog entry on these modules. But there are a lot of them out there…
So – let’s move over to the last part of this blog entry – the Gruntfile.js and the relevant snippets of it for testing with Jasmine
Somewhere in the grunt initConfig, include a section
basepath: { app: 'src/main/webapp/app', dist: dist' }, jasmine: { test: { src: ['<%= basepath.app %>/lib/angular/angular.min.js', '<%= basepath.app %>/lib/jQuery/*.min.js', '<%= basepath.app %>/lib/angular/angular-cookies.min.js', '<%= basepath.app %>/lib/angular/angular-sanitize.min.js', '<%= basepath.app %>/lib/angular-ui/ui-bootstrap-tpls-0.6.0.min.js', '<%= basepath.app %>/lib/angular-translate/angular-translate.min.js', '<%= basepath.app %>/lib/angular-translate/angular-translate-loader-static-files/angular-translate-loader-static-files.min.js', '<%= basepath.app %>/lib/restangular/restangular.min.js', '<%= basepath.app %>/lib/ui-router/angular-ui-router.min.js', '<%= basepath.app %>/lib/underscore/underscore.min.js', '<%= basepath.app %>/lib/angular/angular-resource.min.js', '<%= basepath.app %>/lib/angular/angular-mocks.js', '<%= basepath.app %>/js/app.js', '<%= basepath.app %>/js/**/*.js' ], options: { specs: 'test/jasmine/spec/*.js' //specs: ['test/jasmine/spec/mySpec.js'], <-- example for a single spec version: '2.0.1', display: 'full', summary: 'true' } } },
<%= basepath.app %> is setup to a subdirectory – in this case a maven based structure for a web app directory in a standard WAR environment. Feel free to modify this.
This snippet configures the Jasmine console runner via PhantomJS to run the all test files specified by “options.specs” ( = test/jasmine/specs/*.js).
For dependencies, it will include all referred libraries mentioned in the array of test.src – so don’t forget to add any necessary libs here.
To save time, you can also use wildcards. For all app-specific javascript files, it is done by including “‘<%= basepath.app %>/js/**/*.js'”.
To activate the setup and call it, define
grunt.loadNpmTasks('grunt-contrib-jasmine'); ... grunt.registerTask('build', [ ..., 'jasmine' ]
in the Gruntfile.
Afterwards, if the gradle-script calls the “grunt_build” – Task, the Jasmine tests will be run. Also in case of failures, this will be visible in the Jenkins output.
Last, but not least, there are some pitfalls you might face in the Jenkins job definition.
It might be necessary – if you’re e.g. behind a company firewall, to provide a local mirror of the PhantomJS – binary distributions for your environments.
To configure the phantomJS node_module to fetch the libraries from the local mirror, inject a property into the build environment:
PHANTOMJS_CDNURL=http://path.to.com/local/distribution/phantomjs
Afterwards, it will be picked up by the gradle build task. From beginning of Version 1.9.14 of node module “phantomjs”, you can also set an environment variable from the local .npmrc file. See the module documentation.
It is always a good idea, to test the build scripts locally before from bottom up. So calling
“grunt jasmine” or “grunt build” first will be a good idea to see any issues in the Grunt scripting before moving over to a “gradle build” as the next level :-).