How to Uglify and Transpile ECMA6 JavaScript with a Maven WAR Build

Recently for a Java/Spring website I was working on I found myself wanting to minify the JavaScript and transpile from EMCA6 -> EMCA5 for better browser support. And I wanted to do this from my Maven build and include those minified and transpiled JS files in the packaged WAR file (I had been just using Google’s apache pagespeed mod to deal with minification for me, which had some issues).

While adding this step to the Maven build I ran into a few hitches which I’ll go over here. I’ve touched projects that had npm and Grunt before but never actually setup a build with them so I’ll touch on a thing or two Maven people might find quirky.

Step #1

First things first, if you haven’t already you’ll want to create a GruntFile.js and a package.json for our Grunt/npm build in the project root. You’ll want to add tasks for uglifying and babel the transpiler we will be using in your GruntFile.js file.

My Grunt file ended up looking like the below:

module.exports = function(grunt) 
{
    require("load-grunt-tasks")(grunt); 

    grunt.initConfig({
        babel: {
            options: {
                sourceMap: true,
                presets: ['babel-preset-es2015']
            },
            dist: {
            files: [
                {
                    expand: true,
                    cwd: 'src/main/webapp/resources/js',
                    src: ['*.js', '**/*.js', '!**/libs/**'],
                    dest: 'target/dist/resources/js'
                }
            ]
            }
        },

        uglify: {
            build: {
                files: [{
                    expand: true,
                    cwd: 'target/dist/resources/js',
                    src: ['*.js', '**/*.js', '!**/libs/**'],
                    dest: 'target/dist/resources/js'
                }]
            }
        }
    });
     
    grunt.registerTask('default', ['babel', 'uglify']);
}

And you’ll also need a package.json with the required dependencies. If you don’t have a package.json already you can run npm init to create a started one for you. You’ll want to add dependencies for at least babel, babel-preset-es2015, grunt-babel and grunt-contrib-uglify.

My package.json ended up looking like the below:

{
  "name": "nevergiveup",
  "version": "0.1.0",
  "devDependencies": {
    "grunt": "~0.4.5",
    "grunt-babel": "^6.0.0",
    "grunt-contrib-jshint": "~0.10.0",
    "grunt-contrib-nodeunit": "~0.4.1",
    "grunt-contrib-uglify": "~0.5.0",
    "load-grunt-tasks": "^3.5.2"
  },
  "main": "GruntFile.js",
  "dependencies": {
    "babel-preset-es2015": "^6.18.0",
    "babel": "^6.5.2",
    "grunt-cli": "^1.2.0",
    "grunt-contrib": "^0.11.0",
    "grunt-contrib-coffee": "^0.10.1",
    "grunt-contrib-clean": "^0.5.0",
    "grunt-contrib-compass": "^0.7.2",
    "grunt-contrib-concat": "^0.4.0",
    "grunt-contrib-connect": "^0.7.1",
    "grunt-contrib-compress": "^0.8.0",
    "grunt-contrib-copy": "^0.5.0",
    "grunt-contrib-csslint": "^0.2.0",
    "grunt-contrib-cssmin": "^0.9.0",
    "grunt-contrib-imagemin": "^0.7.2",
    "grunt-contrib-htmlmin": "^0.2.0",
    "grunt-contrib-handlebars": "^0.8.0",
    "grunt-contrib-jade": "^0.11.0",
    "grunt-contrib-jasmine": "^0.6.5",
    "grunt-contrib-jshint": "^0.10.0",
    "grunt-contrib-jst": "^0.6.0",
    "grunt-contrib-less": "^0.11.4",
    "grunt-contrib-qunit": "^0.4.0",
    "grunt-contrib-nodeunit": "^0.3.3",
    "grunt-contrib-requirejs": "^0.4.4",
    "grunt-contrib-sass": "^0.7.4",
    "grunt-contrib-stylus": "^0.15.1",
    "grunt-contrib-symlink": "^0.3.0",
    "grunt-contrib-uglify": "^0.4.1",
    "grunt-contrib-watch": "^0.6.1",
    "grunt-contrib-yuidoc": "^0.5.2",
    "load-grunt-tasks": "^3.5.2"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Step #2

Next we will be using the lovely frontend-maven-plugin which allows you to call npm and grunt from your build and installs npm and node locally by default. To use it you’ll want to add something like the below example plugin section to your build element in your pom.xml

                    <plugin>
                        <groupId>com.github.eirslett</groupId>
                        <artifactId>frontend-maven-plugin</artifactId>
                        <version>1.2</version>

                        <configuration>
                            <nodeVersion>v4.6.0</nodeVersion>
                        </configuration>

                        <executions>
                            <execution>
                                <goals>
                                    <goal>install-node-and-npm</goal>
                                </goals>
                                <phase>generate-sources</phase>
                            </execution>

                            <execution>
                                <id>npm install</id>
                                <goals>
                                    <goal>npm</goal>
                                </goals>
                                <configuration>
                                    <arguments>install</arguments>
                                </configuration>
                            </execution>

                            <execution>
                                <id>grunt build</id>
                                <goals>
                                    <goal>grunt</goal>
                                </goals>
                                <phase>generate-sources</phase>
                            </execution>
                        </executions>
                    </plugin>

Note I noticed npm will occasionally fail to download a package correctly and leave the corrupt download in the global cache, something that just doesn’t happen with our glorious Maven. If you get something like a zip error while running the npm part of the build it’s likely this and you’ll want to run “npm cache clean” from the command line and delete your “node” and “node_modules” folders in your project and try again. (Sighh JavaScript people, it’s 2016 how is this really a problem for you guys still?)

Step #3

Ok with the npm and Grunt stuff all setup were almost good to go. The last thing is getting the minified/transpiled JavaScript files into the WAR correctly. I bashed on this one for a tad to long trying obvious things like copying the minified files over the ones in the target exploded WAR folder at various build phases which didn’t work. That then led to me trying to get the JavaScript resource files excluded and the minified ones included in a pom resources section. Nope that ended up not panning out other, the easiest solution ended up being to just overwrite the files in the maven-war-plugin configuration section.

Add the below to element to your war plugin configuration section and the whole plugin section will look like this:

           <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <warName>NeverGiveUpWeb-1.0-SNAPSHOT</warName>
                    <webResources>
                        <resource>
                            <directory>${project.build.directory}/dist</directory>
                        </resource>
                    </webResources>
                </configuration>
            </plugin>

And it should work if you used the same path scheme of “target/dist” in the Grunt build like I did.

Hope this helped and thank you for reading!

Only cool people share. You do wanna be cool right?Share on Reddit0Share on Facebook0Share on StumbleUpon0Tweet about this on Twitter0

Leave a Reply

Time limit is exhausted. Please reload the CAPTCHA.