Apache Maven is a popular build management tool that we use at Scott Logic to handle our internal projects. When I began using the Closure Tools, integrating the Compiler into the Maven build process became an important task.
Having the compilation of code in the build process assures that the code will be built correctly both at development and release time, as well as ensuring repeatability in the build. We also chose to integrate Closure Linter into the build process to ensure adherence to style. This blog article will aim to show you one technique for including Closure compilation and style checking into your Maven project.
Closure Compiler
Let's imagine we want to include the following builder configuration into Maven:
Maven Integration
Integrating an executable with Maven is simple using the Maven Exec Plugin. However, the tricky part comes with requiring all the source files in one location to compile.
It's to be assumed that you've been using the war plugin to package your web project. The war:war goal will explode all the required files in a target directory, and then package them into a WAR file. The uncompressed files remain in the target directory after the WAR has been created, and we will use these exploded files to compile the code.
The goal is to have all the JavaScript source files in the Maven directory before creating the final WAR file, so we can compile them. We do this by adding a war:war goal to the prepare-package phase. This goal explodes all the required source into the target directory, and allows us to compile, before the actual packaging is done in the package phase.
In its simplest form, the phases are as follows:
- [prepare-package(war:war)] Explode the WAR file into the target directory, then create an empty WAR file from the files just exploded.
- [prepare-package(exec:exec)] Compile the exploded files that were left behind by the war goal of the prepare-package phase.
- [package(war:war)] Create a WAR with the compiled source in it, and no other JavaScript sources.
This can get a little complicated, so I've put together a small table showing the phases:
Phase | Goal | Configuration | Description |
---|---|---|---|
prepare-package | war:war | Creating a WAR archive will explode all files into the target directory. This is how we get all the files we want to compile. | |
only include JavaScript source files | We only want JavaScript source files - don't include any other files | ||
exclude everything from archive | Maven will automatically create an archive from the exploded directory. Although this is the goal, we aren't interested in the war file (we only want the contents of the exploded directory). Don't include anything in the war to save processing. | ||
exec:exec | The exec goal is what will execute the compiler python script. | ||
output to target folder | Ensure that the compiled file is output to the target folder, so when we package, we can include it. | ||
package | war:war | Now that we have the compiled file, we can create the WAR archive. | |
exclude all JavaScript sources except the compiled file | Only include the compiled file (no other JS sources) as they are contained within the compiled file. |
Here's the XML that reflects this:
war:exploded
Note that we could have used war:exploded in the prepare-package phase as the packaged war file isn't required (war:exploded does the job of war:war but without the last step, where the exploded files are added to a war archive).
However, war:exploded doesn't have the same optimization of ignoring dependency changes as war:war, so while it would work the build would be much slower.
Dependencies
It's better practice in Maven to keep individual modules separate, so that you can build against specific versions and therefore improve the repeatability and reliability of your builds. For this reason, you probably won't want to have your application and the Closure Library in the same project, which can be achieved by using a War overlay.
As long as both projects are war packaged, the war goal will explode all files into the same directory. For example, your structure may be similar to the example below:
As you can see, the target directory in myproject has combined the js folders from both projects, so giving the Compiler access to the js folder will include all JavaScript source.
Closure Linter
As another step, you may wish to include a Closure Linter pass in your build process. This is done a very similar way, albeit with fewer complications. Using the same Exec Plugin (remember your dependency!) we can add another execution to the prepare-package phase as such:
The build will then output the results of the style check to the standard output. Of course, if you wanted to use fixjsstyle too, this would only require changing the executable tag.
Conclusion
Hopefully this article has explained the method of integrating the Closure Compiler into your build process. Whilst in theory it doesn't sound like a complicated task, the management of files between different projects can be quite complex.
The main goal of this article was to introduce the method of exploding the required files into one directory, manipulating the files, and then creating the final WAR.
Hopefully I've provided a framework for working with the Closure Tools and Maven and that, in theory, different executables and Maven goals can be applied to the source files (jsdoc-toolkit and JSLint as examples) before they are packaged into the final WAR.