The JVM Fanboy Blog 

Use modern front-end tools in Maven

by vincent


Posted on Saturday Apr 16, 2016 at 05:41PM in Build Tools


After years of owning a static - well, very static, neglected would be a better word - personal WordPress-powered website, I decided to start creating a new custom web application that will demonstrate my various web-dev capabilities and will also serve as my personal website. I can't say I will miss PHP for even a second ;-)

The site will use various back-end and front-end technologies interchangeably, for example parts of the back-end are written in Java 8, Groovy and - of course! - Nashorn. I normally would probably not do this on typical small production applications, but for a demo site I can justify this choice. Thanks to NetBeans IDE's excellent Maven support, I had no issues at all referring Groovy classes in Java code and vice-versa.

The site is under construction and not on-line yet at the time of this writing.

Front-end tools

As I chose Facebook React as one of my front-end toolkits (only some interactive pages of my site will use it. At this time I won't use it globally for the whole site) with JSX to define views, I had to use modern front-end build tools for building the front-end.

Various tools exist to automate buildings tasks, such as the task of calling Babel to convert the JavaScript with JSX to plain JavaScript files. I also chose to do dependency management with external tools. My choice have been to use:

  • Node.js and NPM.js (for installing and running dependencies needed by the build tools)
  • Bower (front-end dependency management)
  • Babel (to convert JSX to plain JS code)
  • Gulp.js (task-runner to automate the building and packaging)

Integrating front-end building tools with Maven

For this project I chose Apache Maven for building the back-end of the application. I know a lot of people don't really like Maven, but since I could follow the Maven convention quite well, I actually had a very pleasant time creating and using the project's pom.xml file file.

At this time the back-end is tightly coupled to the front-end: I wrote my HTML templates in Thymeleaf where applicable, but the corresponding JavaScript and CSS files are stored in a separate "static" directory. This is not ideal, but not really a big deal in my application. Also, I use Nashorn to compile some JavaScript files of the front-end code, so the back-end codes needs access to the front-end code in this application anyway.

Note to self: Once the site is up and running, I want to experiment with ditching the Thymeleaf templates completely and let Nashorn create the whole HTML dynamically using the static JS files (as mentioned above, I already let Nashorn generate HTML for AJAX content on the back-end using the same code as the front-end uses, so I already did some initial work on this).

As it is right now, it made sense to make building of the front-end part of building the whole project.

Some older discussions on StackOverflow suggested this had to be done manually by calling scripts via Maven. Some developers use Ant tasks for this (which I planned to do as well, I think Ant tasks are really suited for this kind of work and those can easily be called by Maven as part of the building process).

Introducing the "frontend-maven-plugin" Maven plugin

However, after some more googling, I came across the frontend-maven-plugin by Eirik Sletteberg. This plugin has a lot of tricks up its sleeve. Features include:

  • Installing Node.js and NPM locally for the built project only.

    This installation won't interfere with Node.JS/NPM that are already installed globally on the system and is only used to install the dependencies and execute the configured tools. This should work on modern Windows, OS X and most popular Linux systems.

  • Execute "npm install" to install the dependencies required for building the front-end
  • Execute "bower install" to install the dependencies required by the front-end (Bootstrap, or in my case Foundation Zurb 6, FontAwesome, etc.)
  • Execute "gulp build" to run the various build task (as described above in my case this will call Babel to convert JSX to JS). Note that the plugin supports Grunt as well.
  • Execute front-end unit-tests using Karma (I have not tried this feature yet, but I intend to try Karma soon)
  • Execute WebPack module bundler (I have not yet tried this)

Configuring front-end-maven-plugin in pom.xml file

Adding those tasks to pom.xml was easy, the examples on the website were simple and easy to follow.

I made up my own directory convention. I chose to create a new "frontend" directory in my project's "src" directory to store all front-end related files. That directory contains the package.json (for NPM), gulpfile.js and bower.js files. I let Gulp create a dist directory here that contains the built files.

The directory structure looks like this:

NetBeans screenshot of my project's directory structure. All front-end files are stored in the 'frontend' directory of the standard 'src' directory

Once built, I let Maven copy the full content of the "dist" directory to the project's resources/static directory as part of the project's build process, using the standard maven-resources-plugin.

In the frontend directory the "node_modules", "bower_components" and "dist" are stored (created by the different tools). I chose to also store the node.JS and NPM installation here in a directory that I called "node_maven". I made sure all those directories are ignored by my version control system, that's why those directories are colored gray in the screenshot above.

Here are the relevant entries in my pom.xml file:

    <build>
         ....
        <plugins>
         ....
            <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <version>3.0.0</version>
                <configuration>
                    <followSymlinks>false</followSymlinks>
                    <filesets>
                        <fileset>
                            <directory>src/main/resources/static</directory>
                        </fileset>
                        <fileset>
                            <directory>src/main/frontend/dist</directory>
                        </fileset>
                    </filesets>
                </configuration>
            </plugin>            
            <plugin>
                <groupId>com.github.eirslett</groupId>
                <artifactId>frontend-maven-plugin</artifactId>
                <!-- Use the latest released version:
                https://repo1.maven.org/maven2/com/github/eirslett/frontend-maven-plugin/ -->
                <version>0.0.29</version>
                <executions>
                    <execution>
                        <id>install node and npm</id>
                        <goals>
                            <goal>install-node-and-npm</goal>
                        </goals>
                        <phase>generate-resources</phase>
                    </execution>
                    <execution>
                        <id>npm install</id>
                        <goals>
                            <goal>npm</goal>
                        </goals>
                        <phase>generate-resources</phase>
                        <configuration>
                            <arguments>install</arguments>
                        </configuration>
                    </execution>
                    <execution>
                        <id>bower install</id>
                        <goals>
                            <goal>bower</goal>
                        </goals>
                        <configuration>
                            <arguments>install</arguments>
                        </configuration>
                    </execution>
                    <execution>
                        <id>gulp build</id>
                        <goals>
                            <goal>gulp</goal>
                        </goals>
                        <phase>generate-resources</phase>
                        <configuration>
                            <arguments> build</arguments>
                        </configuration>
                    </execution>
                </executions>
                <configuration>
                    <nodeVersion>v5.10.1</nodeVersion>
                    <npmVersion>3.8.3</npmVersion>
                    <workingDirectory>src/main/frontend/</workingDirectory>
                    <installDirectory>src/main/frontend/node_maven/</installDirectory>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.7</version>
                <executions>
                    <execution>
                        <id>copy gulp dist files to static</id>
                        <phase>generate-resources</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>src/main/resources/static</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>src/main/frontend/dist/</directory>
                                    <filtering>false</filtering>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
             ....
        </plugins>
         ....
    </build>

    

Also shown in the example above is usage of the maven-clean-plugin plugin to clean the generated directories.

Final thoughts

There are risks when using a plugin like this. For example, when Node.JS or NPM change their download URLs, the plugin will have to be updated. Also, it's hard to guess when new tools will arrive that will become more popular than Gulp, Bower, etc. Luckily the plugin is open-source, so anybody can change the code and hopefully adept it to new situations.

Another, probably more serious concern, is that using tools like this for every build will slow-down the build noticeably. From what I understand, the plugin has built-in support for incremental builds when using Eclipse, but I'm not sure how to do this when using NetBeans IDE at this time.

Finally, in this age of micro-services it makes a lot of sense to completely separate front-end and back-end projects from each other.


Running Nashorn scripts in Ant build scripts: The basics

by vincent


Posted on Tuesday Dec 01, 2015 at 09:01AM in Build Tools


When I start a Java project that I think can confirm to the rules of Apache Maven, I use Maven to create build scripts. Nowadays this is in the majority of my web projects.

Occasionally though, there are cases where I want full control of every step and/or want to do a lot of exotic steps. In those (rare) cases, I still manually create Apache Ant XML build scripts. It has so many built-in type of tasks, most of which are easy to use. Unlike many other developers I know, I quite like Ant, especially when also using Apache Ivy for dependency management.

Checking out the new popular choice for building JVM projects, Gradle By Gradle, Inc., is another high entry on my ever -growing to-do list. Gotta love a tool that has a cursing elephant as a mascot, to illustrate the usual building frustrations ;-) . I will definitely check Gradle out soon.

As probably well known by now, I am a huge Oracle Nashorn fan. When I started working on my latest project, I figured it could be handy to run Nashorn scripts inside Ant scripts and found multiple solutions that I'd like to discuss. For now let's start with the most simple method.

Script Task

Nashorn is fully compatible with the JSR-223 (aka "Scripting for the Java Platform" standard), which Ant supports. Several enhancements were done in Ant 1.7 to this task, so I assume you use a somewhat recent Ant version.

To simply run Nashorn scripts as part of a Ant target, you can use the <script> task. You can embed the script directly in the build.xml file (yuck!) or point it to an external file containing the script.

A simple build.xml example that embeds the script in the XML file (something that I'd never do in production!)

<?xml version="1.0" encoding="utf-8"?>
<project name="VincentsProject" basedir="." default="main">

	<property name="message" value="Hello!"/>
	
	<target name="main">
		<script language="nashorn" > <![CDATA[
			print(message);
			print(project.getProperty("message"));
			print;
			print(project.name);
			print(VincentsProject.name);
			print;
			project.log("Logged from script", project.MSG_INFO);			
		]]></script>
	</target>
</project>
    

You can run the script by saving above's text in a "build.xml" file, switch to a terminal window (Command Prompt on Windows) and run "ant". The output should be something like:

Console output example

Consult Ant's Script task documentation and note some things here:

  • It would have been much, much better to store the script in an external file in a subdirectory and add a src="./build_scripts/script1.js" alike attribute to your <script> attribute.

  • Ant makes available all defined properties to the external script. That's why
    print(message), print(project.getProperty("message")) and print(VincentsProject.name)
    lines all work. You can disable this behavior by adding the setbeans=false attribute to the <script> element, in that case only the "project" and " self" variables would have been passed to the script and "VincentsProject" and "message" would not have been available.

  • You could use the language="javascript" attribute in the <script> element, but this would run Rhino on Java versions 1.6 and 1.7. It is very likely your Nashorn JavaScript script will not be compatible with Rhino, so I'd recommend to use language="nashorn" attribute, unless you are sure your script is compatible with both, or your project requires Java 1.8 or later anyway.

  • Don't worry about the "manager" attribute. Its default value "auto" should be fine. Or set it to "javax" if you're a purist. From what I understand, Apache BSF was an older scripting standard that predates and inspired the JavaX JSR-223 specification that Nashorn implements.

  • When your Nashorn script uses external Java libraries, you can add a "<classpath>" element to the <script> element, like: <classpath><fileset dir="lib" includes="*.jar" /></classpath>

  • You can also add a classpath by defining a "<path>" element in your build file under "<project>" element; and refer to this inside your script element by using the "classref" attribute. See Ant documentation for more help. Use this construction if you need the same classpath specification for multiple tasks.

  • One thing that not seems possible is to add parameters to the ScriptFactory. AFAIK it's therefore not possible to use Nashorn's shell scripting capabilities when using this Script task.

Note that you can do some funky stuff. You can implement a full Ant task that takes file sets as a parameter. I'll rewrite one of the examples from the Ant documentation in Nashorn and post it here.

In a next post about this subject, I will post about the Scriptdef task, that adds some interesting features.