Thinking about the nature of things we can sometimes notice that a web application, depending of course on the level of sophistication, has in fact dualist nature when it comes down to its ontology. Having too vivid imagination we can relate it to Plato's duality of soul and body, which in case of web application can be a metaphor. As it is so that from the nature of web app we can derive a backend part, powered for example by a Java app, and a front end, where the use of JavaScript is dominant. The backend is soul, powering the body, but the body is what people see.

Both must be functional, both are equally important (ok, Plato would have disagreed with that, so let's leave the metaphor here), and they deserve the proper build process. It is very common though that often the front-end part is mistreated or ignored.

Example of this would be the following build flow:

  • web developer prepares front-end, codes CSS, JavaScript, that of course requires a couple of libraries (jQuery, AngularJS, etc.), maybe a LESS compiler, maybe concatenation of all the files into one to keep the number of requests low. He or she also runs the tests of the app, hopefully.
  • then he COMMITS the whole thing into the release branch of Java application
  • in the meantime the Java developer prepares back-end, runs the tests and commits the whole thing into the release branch
  • the continuous integration server pulls the release branch, builds, runs the tests and deploys

The end result is that the front end is skipped from one of the most important aspects of continuous integration process, as only Java tests are run blocking the app from deployment in case of any errors.

Moreover the release branch contains not only a code produced by project's developers, it's also polluted by artifacts like CSS generated by LESS compiler, or completely external ones like minified source of jQuery, etc.

And what if the whole thing gets to be analyzed by static code analysis tools both in IDE while coding or as a part of the process? Tweaking the configuration and adding per-project exclusions every time they are needed?

The solution to that problem would be to integrate the build process of the front-end into the process of the whole application. So, the tool is needed to handle it, but building the Java app with Maven we already have it - it defines steps to build the app and permits to hook into these steps to perform required actions.

I'm going to use the source code of Spring Boot MVC application from one of the previous articles as a base for that, and it will be available here on GitHub.

Automating front-end build process

This requires some tools from the front-end developer too. Hopefully the JavaScript world gets more and more close to being more mature, so the tools are out there, it's just there are many of them.

First thing is Node.js Package Manager (NPM). As name suggests it permits to download packages for Node.js from a repository.

Hope no one is going to take offense, but Grunt is Maven for JavaScript. It's based on Node.js. It permits to run tasks and has plugins.

Finally there is Bower, another package manager, but this one not tied to Node.js and is perfect for downloading third party libraries like jQuery.

Setting up Grunt via NPM

As prerequisite Node.js should be installed on the development machine, it should also be present in the build agent if using continuous integration solution.

This needs to be added into the project root to the package.json file:

{
    "name": "example-spring-boot-mvc",
    "version": "0.1.0",
    "devDependencies": {
        "grunt-contrib-clean": "~0.4.0",
        "grunt-bower-task": "~0.3.0",
        "grunt-contrib-uglify": "~0.4.0",
        "grunt-contrib-less": "~0.10.0",
        "grunt-contrib-copy": "~0.4.0",
        "grunt-cli": "~0.1.13",
        "grunt": "~0.4.0"
    }
}

It tells NPM to install Grunt and it's plugins. The plugins I'm going to use are:

  • grunt-contib-uglify - uglify makes .js files ugly and compact
  • grunt-contib-less - LESS offers extended syntax (like variables) and comples it to CSS
  • grunt-contib-copy - a task for copying files around
  • grunt-contib-clean - a task for removing files
  • grunt-bower-task - a task for running Bower

Now we need Gruntfile.js, a Grunt equivalent to pom.xml:

module.exports = function (grunt) {
    var webappDir = 'src/main/webapp/';
    var cssDir = webappDir + 'WEB-INF/css/';
    var jsDir = webappDir + 'WEB-INF/js/';
    var imgDir = webappDir + 'WEB-INF/images/';

    var dstDir = webappDir + 'public/';
    var bowerDir = dstDir + 'lib/';
    var dstCssDir = dstDir + 'css/';
    var dstJsDir = dstDir + 'js/';
    var dstImgDir = dstDir + 'images/';

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        clean: [ dstDir ],
        bower: {
            install: {
                options: {
                    targetDir: bowerDir,
                    install: true,
                    cleanTargetDir: false,
                    cleanBowerDir: false,
                    bowerOptions: {}
                }
            }
        },
        copy: {
            images_css: {
                expand: true,
                cwd: cssDir,
                src: [
                    'images/*'
                ],
                dest: dstCssDir
            },
            images: {
                expand: true,
                cwd: imgDir,
                src: [ '**/*' ],
                dest: dstImgDir
            }
        },
        uglify: {
            dist: {
                options: {
                    compress: true,
                    report: 'min'
                },
                src: [
                    jsDir + '*.js'
                ],
                dest: dstJsDir + 'all.js'
            }
        },
        less: {
            dist: {
                options: {
                    compress: true,
                    yuicompress: true,
                    report: 'min'
                },
                src: [
                    cssDir + '*.less',
                    cssDir + '*.css'
                ],
                dest: dstCssDir + 'all.css'
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-bower-task');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-less');

    grunt.registerTask('default', ['bower', 'uglify', 'less', 'copy']);
};

It defines what should happen on Grunt invocation by defining a default chain of tasks in that order:

  • bower - installs bower packages and puts them to src/main/webapp/public/lib
  • uglify - grabs *.js files from src/main/webapp/WEB-INF/js/, minifies, concatenates and puts them to src/main/webapp/public/js/all.js
  • less - grabs *.css files from src/main/webapp/WEB-INF/css/, compresses, concatenates and puts them to src/main/webapp/public/css/all.css
  • copy - copies contents of src/main/webapp/WEB-INF/images/ and src/main/webapp/WEB-INF/css/images to the respectable dirs in/src/main/webapp/public/

Additional task, not in the default chain, is clean that removes the contents of src/main/webapp/public.

Setting up Bower

Now we need to tell Bower which third party libraries to download. Guess what, we need additional file, this time it's bower.json:

{
    "name": "example-spring-boot-mvc",
    "version": "0.1.0",
    "dependencies": {
        "jquery": "~2.1.0",
        "html5shiv": "~3.7.0"
    }
}

Here I have decided I'm going to use jQuery and html5shiv.

Running it by hand

Having Node.js properly installed go to the project dir and type:

npm install

This creates node_modules directory where NPM keeps its stuff. It has Grunt installed, so we can run it:

./node_modules/.bin/grunt

After that you should notice the src/main/webapp/public was generated and contains your generated resources together with third party libraries.

You can also run:

./node_modules/.bin/grunt clean

To clean it up.

Integrating it with Maven

It should get down to running those commands during the build process, preferably with a command to run tests for JavaScript files that should fail in case of errors. We can hook to the process-resources and clean phases of the maven build lifecycle. Simple way to run them would be to use maven-antrun-plugin, so let's add it to <plugins> section of pom.xml:

<!-- Antrun -->

<plugin>
    <artifactId>maven-antrun-plugin</artifactId>
    <executions>
        <execution>
            <id>public-build</id>
            <phase>process-resources</phase>
            <configuration>
                <target>
                    <ant antfile="${basedir}/public-build.xml" target="build"/>
                </target>
            </configuration>
            <goals>
                <goal>run</goal>
            </goals>
        </execution>
        <execution>
            <id>public-clean</id>
            <phase>clean</phase>
            <configuration>
                <target>
                    <ant antfile="${basedir}/public-build.xml" target="clean"/>
                </target>
            </configuration>
            <goals>
                <goal>run</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Now what's left is to prepare public-build.xml it references to run NPM and Grunt:

<project name="public-build" default="build">

    <target name="build">
        <echo>NPM is installing dependencies</echo>

        <!-- npm install -->
        <exec executable="cmd" osfamily="windows" failonerror="true">
            <arg line="/c npm install"/>
        </exec>
        <exec executable="npm" osfamily="unix" failonerror="true">
            <arg line="install"/>
        </exec>

        <echo>Grunt is executing tasks</echo>

        <!-- grunt -->
        <exec executable="cmd" osfamily="windows" failonerror="true">
            <arg line="/c .\node_modules\.bin\grunt --no-color"/>
        </exec>
        <exec executable="./node_modules/.bin/grunt" osfamily="unix" failonerror="true">
            <arg line="--no-color"/>
        </exec>
    </target>

    <target name="clean">
        <echo>Grunt is cleaning up...</echo>

        <!-- grunt clean -->
        <exec executable="cmd" osfamily="windows" failonerror="true">
            <arg line="/c .\node_modules\.bin\grunt --no-color clean"/>
        </exec>
        <exec executable="./node_modules/.bin/grunt" osfamily="unix" failonerror="true">
            <arg line="--no-color clean"/>
        </exec>
    </target>

</project>

Running it via Maven

As it's hooked to the build process, it's just a matter of doing:

mvn clean package

And the compiled resources will be packed into the WAR with the rest of the application.

One small but important thing is to prevent the generated artifacts from being ever committed to the VCS, in case of using GIT,those lines should be in .gitignore:

target
node_modules
bower_components
public

Closing remarks

This solution It's kind of ugly from the perspective of how many tools were used - we have two package managers (NPM, Bower), and two task runners (Grunt, Ant), not counting Maven itself of course. It's partially due to the fact that front end development tools are not so mature, so there is no "one to rule them all" kind of tool. Maven itself by architecture also requires plugins to do some less standard things, like ANT here to run few commands. Perhaps the integration using Gradle would look better.

Nevertheless it allows to treat the build process of the front-end as equally important and use continuous integration or static code analysis tools with it without additional configuration for exclusions.

And what is your approach to this?

This site uses cookies. By continuing to browse the site, you are agreeing to our use of cookies. Find out more in Privacy and Cookies Policy.