Download

Go here to download ready-to-run and source distributions of Jangaroo. more...

Friday, June 8, 2012

Managing JavaScript Libraries with Maven

Jangaroo's mission is to create and apply Enterprise software development tools to Web development. While Jangaroo is known for its ActionScript-to-JavaScript compiler jooc, we also care about "native" JavaScript development. In the process of creating a Maven-based build process for compiled ActionScript, we were baffled that there was not even a Maven-based build process for JavaScript. In fact, there is no version-, dependency- and artifact-managing build process for JavaScript at all (as far as we know).

In the course of creating Jangaroo 2, and utilizing a new feature of Java Servlet 3, we can now present a light-weight and easy way to use Maven for managing JavaScript library versions and their dependencies and adding them to your Web application.

Approach and Related Work

Note that we are not the only ones who provide Maven JAR artifacts containing repackaged JavaScript libraries. Independently, James Ward created a project called WebJars, which does something quite similar as proposed here. He also uses JARs and Maven, but the difference is that he is targeting other Web servers and languages, essentially using the JAR as a ZIP. Our approach is to use the Servlet 3 JAR layout, essentially putting all Web resources under the path META-INF/resources. The main advantage is that every Servlet 3 compliant application server (Jetty 8, Tomcat 7, Glassfish 3, Resin 4, WebSphere 8, WebLogic 12) understands this standardized format (JSR-315) immediately without any plugin, Servlet or other extension. Another benefit is that you can add files to the JAR that are not supposed to be Web resources, e.g. other meta data and of course Java classes, so that you could for example unite code implementing the client- and server-part of some service in a single JAR.

An organizational advantage we have over James' artifacts is that we deploy all repackaged artifacts of Open Source libraries (almost all JavaScript libraries are Open Source!) directly to the Maven Central repository, saving you the need to add <repository> sections to your Maven POM and leaving you independent from our Maven repository being online.

Try It Out!

The first available artifact is a repackaging of Ext JS 3.4.0 (yes, it is an old version, but take it as an example of the mechanism) and a very light-weight example project using this artifact. The following steps are needed once to have an environment to develop Maven-managed JavaScript Web applications:

  1. Install a recent Java SDK like 6 or 7
  2. Install Apache Maven, current version 3.0.4
  3. [Install git]
Checkpoint: calling mvn -v on a shell should give you output similar to this:
> mvn -v

Apache Maven 3.0.4 (r1232337; 2012-01-17 09:44:56+0100)

Maven home: ...
Java version: 1.6.0_25, vendor: Sun Microsystems Inc.
Java home: ...
Default locale: ...
OS name: ...


To try the Ext JS example project, download or clone from github:
> git clone git://github.com/fwienber/webjars-extjs-example.git

Then, all you need to do is build the project (which downloads all needed stuff from Maven Central when invoked for the first time) and start a Jetty Web server via Maven, simply via
> mvn jetty:start
The default port is 8080, so you can watch the resulting Web app in any browser using http://localhost:8080. To prove that Ext JS has been loaded and works, the app just opens an Ext alert box.

Only three small text files are needed to
  • download the desired Ext JS library,
  • download  Jetty webserver,
  • configure Jetty to serve the contents of the Ext JS library and the local project resources, and
  • start Jetty in development mode on localhost port 8080.
The three files are
  1. pom.xml ―The Maven "Project Object Model" that configures the build,
  2. src/main/webapp/index.html ―the HTML file containing the simply JavaScript application,
  3. src/main/webapp/WEB-INF/web.xml ―Java Web configuration file.
Let me give you a walk-through of these three files.
pom.xml is the key to letting Maven create and manage your application. Fortunately, this POM is quite simple, since we take advantage of conventions, and Maven prefers convention over configuration.

<?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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>net.jangaroo.webjars.examples</groupId>
  <artifactId>webjars-extjs-example</artifactId>
  <version>0.1-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>webjars-extjs-example</name>
  <description>...</description>

  <build>
    <plugins>
      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>jetty-maven-plugin</artifactId>
        <version>8.1.3.v20120416</version>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>net.jangaroo.com.sencha</groupId>
      <artifactId>ext-js</artifactId>
      <version>3.4.0</version>
    </dependency>
  </dependencies>
</project>

After the inevitable XML header (which is always the same), every Maven project model needs to specify a unique combination of groupId and artifactId, and a version. For this test project, they really do not matter, since the generated artifact will never be deployed to a repository. The packaging type war tells Maven that we are building a Web application (Java terminology: Web ARchive). name and description are just for human readability.
The only plugin that needs to be mentioned explicitly is jetty-maven-plugin, and you also need to specify the exact version you want to use. Well, it's still only a couple of lines to download and start a Web server, isn't it?
To download a serve all Ext JS 3.4.0 resources, a simple dependency suffices, thanks to the standard-conform JAR layout and central deployment of the artifact.

index.html is the simplest HTML page you could think of that starts an Ext JS application. The only interesting thing here is how the resource paths have been chosen, because you do not actually "see" the Ext JS resources―they are hidden in a JAR somewhere in your local Maven repository (see below). For every JavaScript library repackaging, we have to agree upon a standard path where it is served in the Web application. For Ext JS, we chose /ext-js/ as the base path. The file structure below this base path is exactly as in the ZIP that can be obtained from Sencha's Ext JS 3 download page.

web.xml is only needed to disable a Jetty feature ("file locking") that hinders development, and to satisfy the Maven WAR plugin, which would have to be configured to not <failOnMissingWebXml> otherwise.

The Jetty Maven plugin configures Jetty in a clever way: library dependencies like Ext JS, which have been downloaded to your local Maven repository automatically (usually ~/.m2/repository), are served directly from that JAR. That means no files are extracted, not even the JARs needed by your Web app are copied. All local project Web resources are taken directly from src/main/webapp. Try changing index.html (e.g. the alert message) and reload in the browser: it works without further build or deployment!

If you want a complete Web app that could be deployed into a remote Java application server, you can invoke mvn package, and a directory target/webjars-extjs-example-0.1-SNAPSHOT is created which contains all needed resources. This directory is also zipped as target/webjars-extjs-example-0.1-SNAPSHOT.war.

Unpacking all Web resources contained in JARs, e.g. to deploy them to a static Web server like Apache HTTP Server, is also possible via Maven, but I'll cover that in a later update.

Mighty Maven

What the simple example does not show is that, if ext-js 3.4.0 had a dependency on some other artifact, say ext-core 1.0, you wouldn't have to care about it. Simply specifying a dependency on ext-js lets Maven collect all transitive dependencies and add the corresponding artifacts to your Web application. Furthermore, if two libraries you use depend upon the same third library, Maven takes care of the third library being included only once, and offers dependency management to resolve possible version conflicts.

All in all, given the complexity of the problem it solves, Maven provides a light-weight way to manage complex JavaScript Web applications, making it easy to keep track of libraries used, their versions, and their dependencies. Furthermore, Java Servlet 3 helps structure a Web application as a composite of modules instead of a huge bunch of files, which speeds up build process and test system start-up.

What Next?

Next is to repackage and deploy or release several JavaScript libraries. There are several options, so here we need your vote and maybe even your help. We plan to repackage the libraries and versions we (plan to) use at CoreMedia, for example jQuery 1.7, Ext JS 4.1.0, RequireJS 2.0.1, and CKEditor 3.6.2. If you need other JavaScript libraries or other versions, you can simply create an issue at JooTrack/LIBS. We need the exact location of a download package or all single download links, and if you could provide additional information which files are usually needed in an actual deployment (e.g. no examples, no documentation, ...), it would help a lot. So start refactoring your large-scale JavaScript project today―clean up the JavaScript mess!

6 comments:

Unknown said...

detailed & very helpful... thumbs up!

Moandji Ezana said...

Interesting. An accepted standard might indeed be preferable to James Ward's solution, as long as deployment isn't too complicated.

Where is /ext-js defined as the base path? I looked through both your example and ext-js repos but couldn't find it.

Does this play well with other web asset optimisation tools?

Frank said...

@Moandji concerning deployment, I added a Maven profile to the github example project to generate an "exploded" Web app, i.e. unpack all resources from the JARs. Just invoke mvn -Pwebapp package, and find the exploded Web app under target/dependency/META-INF/resources.
To ease uploading, the profile could be extended to create a ZIP.

The base path /ext-js is defined in the ext-js pom.xml. The idea is that you just add the original packaged download to src/main/original, and all the repackaging instructions are contained in the POM.
The WebJars community is currently discussing the layout of WebJars, e.g. whether the path should contain groupId and/or version information.

Anonymous said...

"In fact, there is no version-, dependency- and artifact-managing build process for JavaScript at all (as far as we know)."

What about Node's npm? I believe that's a standard solution to dependency management in JavaScript world these days.

Frank said...

Yes, Node's npm and frameworks like RequireJS also provide solutions for dependencies between modules, but at run-time. Maven solves the module dependency problem at build-time, so the two could complement each other. Maven can automatically download needed modules in the exact desired version from possibly decentral repositories. A module is not only a *.js file, but an "artifact" bundled from many files, like code and resources (images, CSS). Updating to the latest version of a required module is done by simply changing the version number in your POM and rebuilding the project.

wayne said...

Great work guys.
I'd actually tried this myself a few years back with ivy and ivy-svn with ant.
Anyway, my vote (if no one else has mentioned it) would be for Mr. Crockford's json2 lib.
json2 git hub site