Eclipse IDE configuration: TypeScript in a Java EE/Maven pipeline

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn

Editor’s Note: I did this experiment to test out how putting together everything under a single IDE in a single project worked; I discourage this single project approach and I suggest instead to split the web front-end into a separate project, built with an appropriate toolchain.

FrankenIDE

Moreover, while Eclipse TypEcs works fine, you’d be probably better off with a web-first IDE, like JetBrains WebStorm

For the curious developer, this blog post describe what the setup and the development experience of a single Maven war/TypeScript project looks like, so you can try it yourself and see if it suites your personal tastes.

Before we dig in the code, a little more context. JavaScript is a perfectly fine language for development, but as applications grow and technologies mix, long term maintenance costs hinders more and more feature development, as refactoring is a hard proposition on a loosely typed language. This is especially true when using inversion of control based frameworks and projects having heavy code reuse thorough the application.

 

There are many languages that compile to JavaScript, mostly focused in enabling cleaner code by introducing static typing or constraining the development model in other ways. The promises they make are tempting, especially to full stack developers that are more used at the conveniences typical of the back end world and feel out of their comfort zone in the pure JavaScript world of prototypes, even if in the end it is not that hard to learn the language itself.

Why?

 

Why TypeScript in particular? Well, it is one of the few languages that had all the features a back-end developer misses when switching to a prototype oriented language, and then more:

  • typed – well, duh
  • mix and matches with JavaScript
  • loads of bridges available to libraries like Backbone.js
  • debugger link from browser to IDE code
  • incremental compilation support
  • batch compilation and aggregation available for production builds

 

The thing that matters here is the whole toolchain. It is of limited use a compile to JavaScript language where you need to rebuild the whole project to test a single file change. The development changes should reflect live on the local development environment, as waiting for compilation, however little, can knock developers out of the focus zone.

 

With that in mind, TypeScript provided the most tooling: an Eclipse plugin for incremental compilation, Grunt plugin for builds, browser to Eclipse bridge for debugging and enough flexibility to have files show up where Eclipse expects them in a Java EE Dynamic Web project.

 

ezgif-1488061189

 

 

First of all, make sure you have TypEcs, node.js and Maven installed and configured.

I started with the standard maven war project creation, the structure is pretty simple, with a TypeScript folder to hold the .ts right within the web content resources. Then I added all the stuff about Grunt to perform the build from the command line:

tree

 

The pom.xml is the main file here, and it runs node via the antrun plugin, using the code from this post:

<plugin>
  <artifactId>maven-antrun-plugin</artifactId>
  <version>1.7</version>
  <executions>
  <execution>
  <phase>generate-sources</phase>
  <configuration>
    <target name="building">
    <echo>
    <!-- NPM INSTALL -->
    </echo>
    ...
    <exec executable="cmd" dir="${project.basedir}" osfamily="windows"
    failonerror="true">
    <arg line="/c npm install" />
    </exec>
   ...
    <echo>
    <!-- GRUNT -->
    </echo>
    <exec executable="cmd" dir="${project.basedir}" osfamily="windows"
    resultproperty="cmdresult">
    <arg line="/c grunt --no-color > grunt.status " />
    </exec>
    ...
    <loadfile property="grunt.status" srcFile="grunt.status" />
    <echo>${grunt.status}</echo>
    ...
    </target>
  </configuration>
  <goals>
  <goal>run</goal>
  </goals>
  </execution>
  </executions>
</plugin>

 

We need Grunt available from the command line, and I found no other solution than a pre-installation script within the package.json:

{
  "name": "faciliter-javascript",
  "version": "0.0.1-SNAPSHOT",
  "scripts": {
   "preinstall": "npm i -g grunt grunt-cli grunt-contrib-jasmine grunt-typescript"
  }
}

 

For the actual TypeScript compilation, I partly followed the instruction found in this blog, so I’ll spare you the full Gruntfile.js; there are however a couple significant differences highlighted next.

The folders are changed so I have the .ts files directly within the webapp maven folder, likewise, the generated files are located in the same folder, so that I need no additional packaging step and the generated js files are right where the IDE expects them:

... 
dir : {
  // location where TypeScript source files are located
  "source_ts" : "src/main/webapp/ts",
  ...
  // location to place (compiled) javascript files
  "target_js" : "src/main/webapp/ts",
  ...
},
...

 

Another change to the suggested script is that I run the compilation both with and without layering; compiling without layering is the default:

compile : {
  src : [ '<%= dir.source_ts %>/**/*.ts' ],
  dest : '<%= dir.target_js %>/',// with layering:<%= project.name %>.js',
  options : {
    basePath : '<%= dir.source_ts %>',
    target : 'es5',
    declaration : true,
    comments : false,
    sourceMap: true
  }
},

In the html file, I load the layer first, then use a onerror handler to detect the layer script missing failure:

<script type="text/javascript">
 var loadUnlayered = function() { 
 var head = document.getElementsByTagName('head')[0];

 var script = document.createElement('script');
 //this shouldn't be manually maintained
 script.src = 'ts/hello.js';
 script.type = 'text/javascript';

 head.appendChild(script)
} 
</script>
<script type="text/javascript" src="ts/layered.js" onerror="loadUnlayered()"></script>

This allow the application to run unchanged from the IDE, where the layer file don’t exist and only the single files are generated. Why that? Because developers using the IDE can’t constantly call maven, that would be too annoying. I rely instead on the TypeScript plugin for incremental compiling changed files. Note the matching configuration so files ends always up in the same place irregardless of the builder:

config

This means that during development the layer file is ignored and the application runs directly from the incrementally built files. In production, one just need to enable layer generation and let the war plugin package it in the final app. This is the task I use conditionally for the production-type builds in the Gruntfile.js:

compile_layer : {
  src : [ '<%= dir.source_ts %>/**/*.ts' ],
  dest : '<%= dir.target_js %>/layered.js',
  options : {
   basePath : '<%= dir.source_ts %>',
   target : 'es5',
   declaration : true,
   comments : false,
   sourceMap: true
  }
},

This is almost everything explained. There is a minor issue with Eclipse not refreshing the workspace after compilation, already solved and pending release.

Here is a link to the shown code: https://www.dropbox.com/s/31jyvj3ce7uy7x2/faciliter.zip?dl=0

This version always builds the layer file, as I added production and development maven profiles on the real project, not this stub. Also, it comes with no support and a ‘works for me’ attitude XD

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn

Leave a Reply

Your email address will not be published. Required fields are marked *