Developing Javascript Web Apps with Maven: Initial Research

Monday, May 13, 2013

In this post I'm going to write down my initial research on creating a Maven based toolset to develop Javascript intensive web applications. The guys at Codehaus already have a project with the same goal, and I've borrowed some ideas from them. His approach doesn't fit well with my needs, but you should take a look a it, maybe it works for you.

I've just started to scratch the surface and I don't address things like Javascript minifying, AMD and the like. These are the problems I've been working on

  • Using a separate directory to store Javascript source and test files.
  • Packaging Javascript libraries as Maven dependencies and using the Maven dependency mechanism to retrieve transitive dependencies.
  • Including Javascript unit tests in the Maven builds
  • Enabling rapid develop/test cycles

Javascript Sources as First Level Citizens

If you are developing a Javascript intensive web app it makes no sense to hide Javascript sources under the webapp directory. So I use a js directory under both, src/main and src/test

directories. Then a I configure the mave-war-plugin to put those files in the proper place at package time:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-war-plugin</artifactId>
  <version>2.3</version>
  <configuration>
    <webResources>
      <resource>
        <directory>src/main/js</directory>
        <targetPath>js</targetPath>
      </resource>
    </webResources>
  </configuration>
</plugin>

Javascript Libraries as Maven Dependencies

Coming from a Java/Maven background, when you start developing Javascript intensive apps thing you miss more is a centralized artifact repository for Javascript libraries. Codehaus is doing a timid attempt to use the central Maven repository to host Javascript dependencies. Codehaus's approach has a major flaw (IMHO), it deals only with Javascript sources, forgetting about other types of files usually bounded to a given dependency. For example, a jQuery UI dependency should include all, the Javascript source and the associated CSS and image files.

My solution (borrowed from this blog post relies on a feature introduced by the Servlet 3.0 API. Basically any file in the META-INF/resources directory within any JAR in the WEB-INF/lib is served a a static resource by the webapp. As an example, this is my Maven project to generate a JAR packaged chosen-jquery

-src
    |-main
    | |-resources
    | | |-META-INF
    | | | |-resources
    | | | | |-css
    | | | | | |-lib
    | | | | | | |-chosen.css
    | | | | | | |-chosen-sprite.png
    | | | | | | |-chosen-sprite@2.png
    | | | | |-js
    | | | | | |-lib
    | | | | | | |-chosen.js
 - pom.xml

And this those are the contents for the pom.xml, including a dependency on jQuery

<?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">
  <parent>
    <groupId>es.cgalesanco.javascript-libraries</groupId>
    <artifactId>javascript-libraries</artifactId>
    1.0-SNAPSHOT
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>chosen-jquery</artifactId>
  <version>0.9.13</version>

  <dependencies>
    <dependency>
      <groupId>es.cgalesanco.javascript-libraries</groupId>
      <artifactId>jquery</artifactId>
      <version>1.9.1</version>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
</project>

When generating these dependencies I'm following a couple of conventions. The main Javascript file is stored in under js/lib without any version designation; this allows changing the version to be used just by changing the Maven dependency, without any change to source files. The same is true for any other file potentially referenceable from source files.

Javascript Unit Tests: Jasmine Maven Plugin

If you are doing Javascript intensive development you should be doing intensive Javascript unit testing, shouldn't you?. I've found Jasmine pretty handy for Javascript unit testing, and the Jasmine Maven plugin integrates this test framework into Maven. I initially had some issues executing the test goal as it used HtmlUnit for running test during the test phase, but from version 1.3.1.2 onwards you can use GhostDriver, a Selenium WebDriver for PhantomJS, to execute tests against a WebKit browser

The Jasmine maven plugin doesn't resolve properly the dependencies inside the META-INF/resources, so I've created a pull request to solve this issue. Meanwhile you can get my modified plugin from here. This is my configuration for the Jasmine plugin (PhantomJS 1.9 must be in your path for the test goal to work):

<plugin>
  <groupId>com.github.searls</groupId>
  <artifactId>jasmine-maven-plugin</artifactId>
  <version>1.3.1.3-SNAPSHOT</version>
  <configuration>
    <jsSrcDir>src/main/js</jsSrcDir>
    <jsTestSrcDir>src/test/js</jsTestSrcDir>
    <webDriverClassName>org.openqa.selenium.phantomjs.PhantomJSDriver</webDriverClassName>
  </configuration>
</plugin>

And this is a minimal Jasmine spec (unit test) taken from the Jasmine web site

describe("A suite", function() {
  it("contains spec with an expectation", function() {
    expect(true).toBe(true);
  });
});

Jetty for Rapid Develop-Test Cycles

Storing your Javascript files in src/main/js has a major drawback for daily development, it doesn't play well with the Jetty Maven plugin. You can use jetty:run-exploded instead of jetty:run, so that the Javascript sources get copied to its final destination within the webapp, but this has two problems:

  1. Generating the exploded web app can be time consuming for any mid-sized application
  2. Changes on the original Javascript sources are not immediatly reflected by the Jetty server (who is serving the copies inside the exploded webapp)

Again, I've submitted a patch to the Jetty project for the Jetty Maven plugin to honor the webResources configuration in the Maven WAR plugin. Meanwhile you can find my modified Jetty plugin from my Github repository