Mix TomEE embedded and Angular 2 with Maven


Since months a typical web application is a JAX-RS for the server-side and a javascript on the client-side.

This powerful architecture can sometimes reveal some challenges in the build pipeline.

However today it is not that hard to mix both frontend and backend build tools to get a single build pipeline easily integrable in a continuous integration solution.

To illustrate that we’ll digg into how to create an Angular 2 application packaged with Maven.

Before starting to speak about the technical solution let see what we need:

  • a java dependency resolution tool
  • a java build tool able to create a .war for java part
  • a javascript dependency tool
  • a javascript build tool
  • a solution to merge both worlds

Java part

The java side is “easy” since we’ll base our build on Maven: Maven does it all for us :). Here is a pom.xml we can use to build src/main/java sources to create a .war:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="           http://maven.apache.org/POM/4.0.0           http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.rmanibucau</groupId>
  <artifactId>sample-angular2</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>Sample Angular2</name>

  <dependencies>
    <dependency>
      <groupId>org.apache.tomee</groupId>
      <artifactId>javaee-api</artifactId>
      <version>7.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.3</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.6</version>
        <configuration>
          <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.tomee.maven</groupId>
        <artifactId>tomee-embedded-maven-plugin</artifactId>
        <version>7.0.0-M1</version>
        <configuration>
          <classpathAsWar>true</classpathAsWar>
          <context>/${project.artifactId}</context>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

I’ll not detail the Java part since this one is quite standard but don’t hesitate to put a comment on this post if you want I write one post dedicated to this setup.

Angular 2 part

In the context of this post we’ll use some NodeJS tools to build our Javascript application:

  • npm: we’ll use it to resolve our javascript dependencies (runtime and build)
  • gulp: the js build tool to compile typescript sources in javascript, uglify sources etc…

For that we’ll use the awesome frontend-maven-plugin. This one will allow us to get a local NodeJS and execute command against main js tools (npm, gulp for us but it supports bower and karma as well to cite a few).

Frontend structure

We’ll put frontend sources in src/main/frontend. This folder will be considered as the root directory for the frontend work (understand it will contain our frontend build configuration files).

Our sources will go into src/main/frontend/js/app for the typescript files (transpiled to javascript later) and our templates in src/main/frontend/partial.

Then we’ll resolve dependencies (node_modules) in src/main/frontend/node_modules to keep tools (IDE) working fine (completion is quite nice isn’t it?).

Finally we’ll build in src/main/frontend/js/app/build (ie the generated .js we’ll be there) and copy these files in src/main/webapp/js to let the normal war build work fine and TomEE Maven plugin support dynamic updates (hitting F5).

Important: we build in the project so you need to add to your ignore file (.gitignore for git these build folders). Here what it can look like for a .gitignore:

.settings
*.iml
.idea
target/
npm-debug.log
node_tmp
build
node_modules
src/main/webapp/js

Cleanup not standard build files

First to ensute we start from a clean build we will remove temp files (build folder and its copy):

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-clean-plugin</artifactId>
  <version>3.0.0</version>
  <configuration>
    <filesets> <!-- this is out app -->
      <fileset>
        <directory>src/main/frontend/js/app/build</directory>
      </fileset>
      <fileset> <!-- this is where we copy our resources (deps + app) -->
        <directory>src/main/webapp/js</directory>
      </fileset>
      <!-- not deleted for time it can take
      <fileset>
        <directory>src/main/frontend/node_modules</directory>
      </fileset>
      -->
    </filesets>
  </configuration>
</plugin>

Important: of course you can delete src/main/frontend/node_modules but this resolution can be a bit slow between runs so in development it is not recommanded.

Get a NodeJS instance

frontend Maven plugin does it for us and support to configure the version we want:

<plugin>
  <groupId>com.github.eirslett</groupId>
  <artifactId>frontend-maven-plugin</artifactId>
  <version>0.0.27</version>
  <executions>
    <execution>
      <id>install-node-and-npm</id>
      <phase>generate-resources</phase>
      <goals>
        <goal>install-node-and-npm</goal>
      </goals>
      <configuration>
        <nodeVersion>v0.12.4</nodeVersion>
        <npmVersion>2.13.0</npmVersion>
      </configuration>
    </execution>
  </executions>
  <configuration>
    <installDirectory>target/node-installation-dir</installDirectory>
    <workingDirectory>src/main/frontend</workingDirectory>
    <skip>${frontend.skip}</skip>
  </configuration>
</plugin>

The things to note on this setup:

  • in the same goal we retrieve NodeJS and npm
  • we download NodeJS in target to ensure it is a a temporary file and in the project workspace. Note: downloading it in a not deleted folder can avoid some network usage; in such a case just add the download folder to the ignored folders for the SCM.
  • we set the working directory to our frontend “root” folder to ensure our commands find our coming configurations
  • we redefined skip option to a maven property. This is cause we’ll use multiple goals of this plugin and each of them have different alias for skip property so if we want to skip the frontend completely with a single property we need an alias

Configure npm

To build an Angular 2 application we need of course Angular 2 dependencies (angular, rxjs, zonejs, …) but we will also rely on gulp and typescript to build our application so we’ll add some dev dependencies for that purpose in our package.json which needs to be in our frontend directory:

{
  "name": "myapp",
  "version": "v0.0.1",
  "description": "app npm config",
  "license": "Apache-2.0",
  "repository": "TBD",
  "dependencies": {
    "angular2": "2.0.0-beta.1",
    "systemjs": "0.19.6",
    "es6-promise": "^3.0.2",
    "es6-shim": "^0.33.3",
    "rxjs": "5.0.0-beta.0"
  },
  "devDependencies": {
    "gulp": "gulpjs/gulp#4.0",
    "gulp-inline-ng2-template": "0.0.10",
    "gulp-load-plugins": "1.1.0",
    "gulp-rename": "1.2.2",
    "gulp-typescript": "2.10.0",
    "gulp-uglify": "1.5.1",
    "typescript": "1.8.0-dev.20151204"
  }
}

Now npm is setup you can of course execute it manually (local installation of NodeJS/npm) from frontend directory but to wire it in our build we’ll add an execution to our frontend plugin:

<plugin>
  <groupId>com.github.eirslett</groupId>
  <artifactId>frontend-maven-plugin</artifactId>
  <version>0.0.27</version>
  <executions>
    <execution>
      <id>install-node-and-npm</id>
      <!-- as before -->
    </execution>
    <execution>
      <id>npm-install</id>
      <phase>generate-resources</phase>
      <goals>
        <goal>npm</goal>
      </goals>
      <configuration> <!-- this is the default args but just to make it explicit -->
        <arguments>install</arguments>
      </configuration>
    </execution>
  </executions>
  <configuration>
    <!-- as before -->
  </configuration>
</plugin>

This new execution will execute npm install in frontend directory and with our package.json it will resolve our dependencies (runtime and dev) in src/main/frontend/node_modules.

Build our application

With Angular 2 it is easier to use typescript which is a kind of rich and typed javascript language. So if we code in frontend in typescript we need to transpile/compile it in javascript.

Angular 2 relies on @Component which can be compared to controllers. They are associated to a view either inline in the annotation (or @View annotation) or through a path. It is nicer to use a path cause you keep all IDE tooling for the view (.html) but for performance reason it is better to inline them. There is a gulp plugin for that purpose we’ll add in our gulp pipeline.

Once all typescript files (.ts) are converted in javascript we will aggregate them in a single .js for performance reasons.

To do both steps we’ll rely on gulp tool which is a kind of ant/gradle for javascript.

Gulp relies on a gulpfile.js file for its configuration. With the dependencies we defined with npm our build can look like:

var gulp = require('gulp');
var plugins = require('gulp-load-plugins')();

var paths = { // keep in mind weqre in frontend folder for paths
    src: {
        ts: './js/app/**/*.ts'
    },
    dest: {
        js: 'build/js'
    }
};

// inline template + compile typescript + concatenate + uglify
gulp.task('compile:typescript', function () {
    var result = gulp.src([paths.src.ts])
        .pipe(plugins.inlineNg2Template({
            base: '/',
            html: true,
            css: false,
            jade: false,
            target: 'es6',
            useRelativePaths: false
        }))
        .pipe(plugins.typescript(plugins.typescript.createProject('tsconfig.json', {
            typescript: require('typescript'),
            outFile: paths.dest.js + '/app.js'
        })));
    return result.js
        .pipe(plugins.uglify())
        .pipe(gulp.dest(paths.dest.js));
});

gulp.task('default', gulp.parallel('compile:typescript'));
  • inlineNg2Template: will inline templates in components
  • typescript: will compile .ts in a single app.js. Note: the aggregation is optional if it doesn’t fit your dev workflow/tooling.
  • uglify: will optimiwe the output .js
  • the final pipe will just dump the file on disk in src/main/frontend/build/js/app.js

To execute this you can call gulp from src/main/frontend manually or use another execution you append to previous one in frontend plugin:

<execution>
  <id>gulp-build</id>
  <goals>
    <goal>gulp</goal>
  </goals>
  <phase>generate-resources</phase>
</execution>

If you are not familiar with typescript plugin, it needs a tsconfig.json as configuration – still in frontend folder – and it can look like:

{
  "compilerOptions": {
    "target": "ES5",
    "module": "system",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false,
    "rootDir": "js/app"
  },
  "exclude": [
    "node_modules"
  ]
}

Copy resources in webapp to be ready to package to test with TomEE embedded Maven plugin

Last step is to copy the dependencies and the built file in src/main/webapp. For that maven-resources-plugin does it very well but any other solution is good for this simple task:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-resources-plugin</artifactId>
  <version>2.7</version>
  <executions>
    <execution>
      <id>copy-frontend-resources</id>
      <phase>generate-resources</phase>
      <goals>
        <goal>copy-resources</goal>
      </goals>
      <configuration>
        <outputDirectory>${project.basedir}/src/main/webapp/js</outputDirectory>
        <resources>
          <resource>
            <directory>src/main/frontend/node_modules</directory>
            <includes>
              <include>angular2/bundles/angular2-polyfills.min.js</include>
              <include>angular2/bundles/angular2.js</include>
              <include>angular2/bundles/http.min.js</include>
              <include>angular2/bundles/router.min.js</include>
              <include>angular2/bundles/upgrade.min.js</include>
              <include>rxjs/bundles/Rx.min.js</include>
              <include>systemjs/dist/system.js</include>
            </includes>
          </resource>
        </resources>
      </configuration>
    </execution>
    <execution>
      <id>copy-app.js</id>
      <phase>generate-resources</phase>
      <goals>
        <goal>copy-resources</goal>
      </goals>
      <configuration>
        <outputDirectory>${project.basedir}/src/main/webapp/js/app</outputDirectory>
        <resources>
          <resource>
            <directory>src/main/frontend/js/app/build/js</directory>
            <includes>
              <include>app.js</include>
            </includes>
          </resource>
        </resources>
      </configuration>
    </execution>
  </executions>
  <configuration>
    <encoding>UTF-8</encoding>
  </configuration>
</plugin>

This is a bit verbose to do a copy but it basically filters the dependencies file we need (minified version of angular and its stack – excepted angular2.js cause in beta1 it has a bug with SystemJS in minified version) and our application (app.js).

Note: if you browsed quickly this post and wonder why we skip the templates (.html): that’s cause now they are in the app.js :).

Dev workflow

The frontend dev workflow is now easy:

  • start embedded tomee: mvn tomee-embedded:run
  • update frontend code/template
  • either use native NodeJS commands or run mvn generate-resources
  • hit f5 in your browser 🙂

Next steps

This basic setup works and is the one of Weekler sample application.

It can be enhanced of few steps (but was already complicated enough for a post):

  • frontend build files can be filtered with maven to get dev/prod flag from the command line
  • we can add a dev profile where files are copied without aggregation and uglification for an easier debugging
  • [up to you] you can migrate to another loader instead of SystemJS

Want more content ? Keep up-to-date on my new blog

Or stay in touch on twitter @rmannibucau

Advertisement

7 thoughts on “Mix TomEE embedded and Angular 2 with Maven

  1. B Tasel

    After running/testing the sample application in development using jetty, how can I configure/deploy the sample application to run inside a Java container (eg. Jboss Wildfly, Weblogic…etc) that would exist in an upper environment?

    Reply
  2. vijay

    I did get the project to work in eclipse.How do we get the project to take ts files changes dynamically,as for each change compiling seems like a hard thing to do.Any guidance will be highly appreciated.

    Reply
    1. rmannibucau Post author

      the setup of the post does it, you can need to set webResourcesCached=false in tomee plugin configuration (https://github.com/rmannibucau/rblog/blob/master/pom.xml#L191 for a sample). Trick is to build frontend resources in docBase (default to src/main/webapp but you can set ${project.build.directory}/${project.build.finalName} which is often easier.

      Eclipse should be able to run tomee-embedded plugin and frontend plugin. It probably have other tools but not sure you manage to integrate them with your build tool (maven/gradle) later easily.

      Reply
    1. rmannibucau Post author

      github.com/rmannibucau/rblog is likely more up to date but it caches node in your home and then you can run mvn frontend:npm@npm-build after one first build (to install dependencies). You can also just go in src/main/frontend and run npm commands there if you installed node the default way and run npm run watch

      Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s