- an API service
- a GUI layer that calls this API
Although the API service is a standard layered Spring MVC application, built and assembled using Maven, the GUI layer is an AngularJS one-page application, that relies on Grunt. It is however obvious that those two modules are two parts of the same application, and I needed a way to integrate seamlessly the Grunt module into a larger Maven structure.
The application is structured this way:
iteach-parent |-- pom.xml // The parent POM |-- iteach-ui // The WAR that serves the API | |-- pom.xml |-- iteach-gui-static // The GUI layer | |-- pom.xml // The GUI module is still considered a Maven module | |-- Gruntfile.js // The Grunt build file, plus other files |-- ... other modules
At the top level, the iteach-gui-static module is still considered a normal Maven module and is registered as such in the parent POM:
<modules> ... <module>iteach-ui</module> ... <module>iteach-gui-static</module> </modules>
The POM file for the iteach-gui-static module uses extensively the exec-maven-plugin in order to call npm, bower and grunt in the correct phases:
<build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <executions> <execution> <id>prepare-env-npm</id> <phase>validate</phase> <goals> <goal>exec</goal> </goals> <configuration> <executable>npm</executable> <arguments> <argument>install</argument> </arguments> </configuration> </execution> <execution> <id>prepare-env-bower</id> <phase>validate</phase> <goals> <goal>exec</goal> </goals> <configuration> <executable>bower</executable> <arguments> <argument>install</argument> <argument>--force-latest</argument> </arguments> </configuration> </execution> </executions> </plugin> </plugins> </build> <profiles> <profile> <id>dev</id> <activation> <activeByDefault>false</activeByDefault> </activation> <build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <executions> <execution> <id>prepare-dev</id> <phase>prepare-package</phase> <goals> <goal>exec</goal> </goals> <configuration> <executable>grunt</executable> <arguments> <argument>clean</argument> <argument>dev</argument> </arguments> </configuration> </execution> </executions> </plugin> </plugins> </build> </profile> <profile> <id>release</id> <activation> <activeByDefault>false</activeByDefault> </activation> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-antrun-plugin</artifactId> <executions> <execution> <id>prepare-release-version</id> <phase>compile</phase> <goals> <goal>run</goal> </goals> <configuration> <target name="replaceVersion"> <echo>Replacing versions in *.json files</echo> <replaceregexp match=""version": "(.*)"" replace=""version": "${project.version}"" byline="true" encoding="UTF-8" > <fileset dir="${basedir}" includes="*.json" /> </replaceregexp> </target> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <executions> <execution> <id>prepare-release</id> <phase>compile</phase> <goals> <goal>exec</goal> </goals> <configuration> <executable>grunt</executable> <arguments> <argument>clean</argument> <argument>prod</argument> </arguments> </configuration> </execution> </executions> </plugin> </plugins> </build> </profile> </profiles>
You can see the complete source code on GitHub.
In the validate phase, I run the following commands:
- npm install
- bower install --force-latest
In dev profile, during the prepare-package phase, I run the grunt clean dev command, that makes all GUI files ready in the local target/dev folder (important, see later).
In release profile, during the prepare-package phase, I do two things:
- replacing the version in the bower.json and the package.json files by the version to release
- run the grunt clean prod command, which packages and compresses all files in target/prod
Now, back in the iteach-ui module (the part that defines the actual WAR to be deployed), I introduce iteach-gui-static as a runtime dependency, in order to force the WAR to be built after the GUI layer has been prepared:
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>iteach-gui-static</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
For the release profile, I just tell Maven to copy the target/prod folder of the iteach-gui-static module just before the packaging:
<profile>
<id>release</id>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-gui-resources</id>
<phase>compile</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>src/main/webapp</outputDirectory>
<resources>
<resource>
<directory>../iteach-gui-static/target/prod</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile>
That's it for the release: the packaged WAR will embed the compressed static resources.
For the development mode, I'm using the tomcat7-maven-plugin and I tell Tomcat to use the target/dev folder of the iteach-gui-static module as the root of static documents:
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<path>/</path>
<contextFile>src/test/resources/tomcat/context-dev.xml</contextFile>
<warSourceDirectory>../iteach-gui-static/target/dev</warSourceDirectory>
...
</configuration>
So, to summarise my set-up:
- a normal AngularJS + Bower + Grunt application declared as a Maven module, with the correct Grunt calls mapped to Maven phases
- a dependency from the WAR to the GUI layer, with a copy of the static resources
When I develop, in a shell or in my Intellij terminal view, I just type grunt watch in the iteach-gui-static folder and launch my Web app using tomcat7:run from within my IDE. The grunt watch command launches a Livereload server that allows my browser to refresh automatically whenever I change one of my static resources. Neat.
At the end, when I release, I just have to rely on mvn package. The necessary NPM + Bower + Grunt calls will be done.
For the complete source code, go to GitHub.
No comments:
Post a Comment