To have front-end build during Maven build cycle has many advantages. Some time ago I wrote about integrating front-end build process into Maven lifecycle, by mostly writing an Ant script installing and running Grunt, and hooking it into Maven.

Since then I've noticed that guys from the previous company I worked in have open-sourced a Maven plugin doing that more cleanly. Also, an alternative called frontend-maven-plugin has appeared.

I'm going to take a look at them.

Using grunt-maven-plugin

Basically, you just add this into the pom.xml:

<plugin>
    <groupId>pl.allegro</groupId>
    <artifactId>grunt-maven-plugin</artifactId>
    <version>1.4.1</version>
    <executions>
        <execution>
            <goals>
                <goal>create-resources</goal>
                <goal>npm</goal>
                <goal>bower</goal>
                <goal>grunt</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Then, by default, your front-end project should be in: src/main/webapp/static (configured by jsSourceDirectory property), together with package.json, bower.json, gruntfile.js.

The plugin requires Node.js Package Manager (NPM) to be installed on the build machine (although an offline mode can be used too). Grunt and Bower need to be on the path too, unless you choose to download them by NPM and run the local copy. During Maven build phase, the contents of jsSourceDirectory are copied to target-grunt (configured by gruntBuildDirectory) in the project's root. Next the npm install is run, followed by bower install and grunt.

The workflow can be completed by using mavenDist Grunt task that comes from grunt-maven-npm module. It's purpose is to copy the contents of a directory which has all output artifacts generated by Grunt tasks to project's target directory. This way they will be packaged into the resulting WAR once the Maven buildcycle completes.

Default configuration vs Spring Boot project's needs

  • In development, the Spring Boot project is run by invoking spring-boot:run, which runs the app extracted rather than from WAR, and expects the resources to be under src/main/webapp (Spring Boot looks also in static or public subdirectories)
  • Downloading and running local copies of Bower and Grunt is preferred for me. This way I have a control over the exact versions I use.
  • I cannot use the Grunt Console in InteliJ Idea - it expects gruntfile.js to be in the project's root, while with this plugin one needs to be with the frontend files, and another copy is created in target-grunt.

Those however could be remedied by using the plugin configuration.

The way I use it

First, let's download a local copy of Bower and Grunt, so the package.json can look like that:

{
    "name": "example",
    "version": "0.1.0",
    "devDependencies": {
        "bower": "~1.3",
        "grunt": "~0.4.0",
        "grunt-cli": "~0.1.13",
    }
}

This provides command-line versions of Grunt and bower in node_modules directory, ater npm install is executed by the plugin. The plugin needs to be told about it using bowerEecutable and gruntExecutable properties.

Next, since I want the output artifacts to be in src/main/webapp/static, where Spring Boot is looking for them, the source front-end files should be moved elsewhere. I personally use src/main/webapp/WEB-INF, simply because when I forget to exclude them from WAR, they cannot be accessed by the Spring MVC servlet. This can be anywhere though.

According to the documentation, the suggested way to have the output artifacts copied anywhere else then target is to maintain a target-grunt/grunt-maven-custom.json file, which is overriding the configuration for Grunt's mavenDist task. This is little too much to accept in my opinion.

My remedy would be to dump using target-grunt entirely instead. There is no obvious gain in having the source files copied into target-grunt and back into the destination, while most of the Grunt tasks are about reading files and writing artifacts. Maybe it's good by the book (a.k.a. "proper integration"), but net gains are more important than rules.

Let the Grunt itself handle copying files where they should be, thus removing a need for mavenDist Grunt task, and this additional configuration file. This is done by removing create-resources goal from the plugin config.

This way we can also keep package.json, bower.json and gruntfile.js in project's root where pom.xml is - makes them easier to find for humans and IDE plugins. The gruntBuildDirectory property should be set to project's root for that to work.

So, the final plugin configuration would look like that:

<plugin>
    <groupId>pl.allegro</groupId>
    <artifactId>grunt-maven-plugin</artifactId>
    <version>1.4.1</version>
    <configuration>
        <bowerExecutable>node_modules/bower/bin/bower</bowerExecutable>
        <gruntExecutable>node_modules/grunt-cli/bin/grunt</gruntExecutable>
        <runGruntWithNode>true</runGruntWithNode>
        <jsSourceDirectory>WEB-INF</jsSourceDirectory>
        <gruntBuildDirectory>${project.basedir}</gruntBuildDirectory>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>npm</goal>
                <goal>bower</goal>
                <goal>grunt</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Using frontend-maven-plugin

Looking for alternatives, there is frontend-maven-plugin, that is actively developed and looks promising. The configuration doing roughly the same thing would look like this:

<plugin>
    <groupId>com.github.eirslett</groupId>
    <artifactId>frontend-maven-plugin</artifactId>
    <version>...</version>
    <executions>
       <execution>
           <id>npm install</id>
           <goals>
               <goal>npm</goal>
           </goals>
       </execution>
       <execution>
           <id>grunt build</id>
           <goals>
               <goal>grunt</goal>
           </goals>
       </execution>
    </executions>
</plugin>

This one also supports downloading and locally installing a given version of NPM into the build machine, which is nice to have when required.

What it lacks at the moment is a direct support for Bower, but it can be invoked by Grunt instead, by using grunt-bower-install.

Summary

Both plugins seems like be a better way than my previous way using Ant script to do the job.

The grunt-maven-plugin is nice, but a little over-done, in a such way that it strictly integrates itself into development process used in the company that has created it. The configuration however is flexible enough to skip unnecessary things, if your process does not match theirs.

The alternative - frontend-maven-plugin looks great, it's configurable, and does what it's supposed to do and no more than that. To fully please me though it needs Bower support.

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.