Thursday, November 4, 2010

JavaScript Dependency Management and JSLint Tools

I've been focusing on JavaScript Rich Internet Application development for some time now and have felt that while JavaScript is a capable language, it has sorely lacked tooling. This situation is changing of course and we're seeing more JavaScript tools come from the likes of Mozilla, Apple and Google quite frequently.

I feel that the lack of dependency management with good version control in any language is painful and JavaScript was no exception. I recently spent a good part of my day sorting out dependencies in a .NET environment; I sorely missed Apache Maven there. Given my JavaScript development activities I decided to provide the dependency management functionality of Maven to JavaScript via a Maven Plugin.

Imagine writing just the following in your JavaScript code when you want to ensure that jQuery or Prototype.js is present at runtime:

var $;

Alternatively when there are no global variables declared by a JavaScript dependency you can import it without being concerned about its file location and version:

/**
 * @import com.jqueryui:jquery-ui
 */

The com.jqueryui:jquery-ui artifact along with its version is declared in Maven's POM file.

This frictionless approach to declaring JavaScript dependency requirements is the motivation for the Maven JavaScript Import Plugin.

I'm pleased to announce the availability of my JavaScript Import Plugin at Codehaus where I have now also become a committer (a great honour!).

Along the way  I decided to create a plugin that provided efficient JSLint invocation during JavaScript development. This plug is also released at the Codehaus and is named the JSLint Plugin.

Please help the professional JavaScript developer community by downloading and building the projects from source and try out some development using the plugins.

11 comments:

ProggerPete said...

Hi Christopher,
I've wanted the same things and am a reasonable way down the track of developing my own, similar, plugin.

I'm not a big fan of doing work that someone else has already done though, so I'm having a look at your js import plugin.

One query I have is whether it's possible to support non global vars? E.g. I'd like to use

/**
* @requires mypackage.MyClass
*/

To import a specific file (and any dependencies of that file), as opposed to the whole artifact.

Christopher said...

Hi ProggerPete,

Yes, absolutely possible to import an artifact outside of using globals. To do this:

/**
* @import com.jqueryui:jquery-ui
*/

Rather than using a class convention (there are no classes in JS!), we use a Maven group and artifact id. This will refer to a dependency declared in your pom.xml.

Please also see the 3rd paragraph of the project page:

http://mojo.codehaus.org/js-import-plugin/

ProggerPete said...

Hi Christopher,
Took a while to notice your response, was expecting an email.. Anyways, I get that I can import whole js artifacts. I was hoping to be more granular. E.g. I've got a project with no dependencies at all. I've got 50 odd js files, some of which depend on other js files inside my application.

It would be handy to be able stick some import/requires type directive at the top of say, my GridView file and then just say. 'Give me GridView and any extra files it needs.

Extending it further, once I've built this artifact I'd like to in another project depend on this artifact and again say, 'give me GridView'. Knowing that I'll get just enough of the js files to run it and no more.

Cheers,
Peter

Cheers,
Pete

Christopher said...

But you can do that! :-)

You can a) either declare what globals you require using the JSLint /*global ... */ convention; and/or b) declare the @import statements.

For example if you have a GridView object declared in GridView.js, and then have /*global GridView */ in the top of your file that requires it, the plugin will ensure that GridView.js is brought in along with any of its dependencies.

Kind regards,
Christopher

ProggerPete said...

Hmm, sounding interesting. The global bit might get me though. what if my GridView is actually declared as my.package.name.GridView? Does it still fly?

ProggerPete said...

Wish I could edit comments... Essentially what I'm asking is can I do

/*global my.package.name.GridView */

Christopher said...

I've not implemented that level of parsing sophistication, but as I'm using ANTLR, it is not beyond the realms of possibility. Feel free to contribute to the project!

What you can do though is:

/*global my */

or have an @import for the artifact that declares the GridView e.g.:

/**
* @import my.package.name:GridView
*/

(not withstanding better naming as per GAV conventions in Maven)

Emphyrio said...

Hi, I'm trying out your plugin, but I ran into a problem with transitive dependencies. I have a file foo.html which includes foo.js, which imports bar.js, which imports baz.js. In foo.pom, I declare a dependency for bar.js. When I installed bar.js in my local Maven repo, I specified bar.pom which declares a dependency on baz.js. So it looks like the following:

foo.html:
<script type="text/javascript" src="${foo.js}"></script>

foo.js:
/**
* @import com.example.foo:bar
*/

foo.pom:

com.example.foo
bar
1.0
js


bar.js:
/**
* @import com.example.foo:baz
*/

bar.pom:

com.example.foo
baz
1.0
js


However, during the main-import-js task, it threw an exception

Caused by: org.apache.maven.plugin.MojoExecutionException: Build stopping given dependency issue.
at org.codehaus.mojo.jsimport.AbstractImportMojo.processFileForImportsAndSymbols(AbstractImportMojo.java:842)
at org.codehaus.mojo.jsimport.AbstractImportMojo.buildDependencyGraphForDependencies(AbstractImportMojo.java:458)

I changed line 458 of AbstractImportMojo.java as follows:

LinkedHashSet artifactTransitives = directArtifactWithTransitives.get( directArtifact );
if ( processFileForImportsAndSymbols( artifactFile, fileDependencyGraphModificationTime, artifactTransitives ) )

This seems to have fixed the problem. Do you think this change would have any undesirable side effects?

Christopher said...

If baz.js needs to be imported by bar.js then it is a direct dependency, not a transitive dependency. Thus you would need to specify that your project depends on both bar.js and baz.js.

It is great to have the feedback, but can I ask that you post to the Mojo users forum? To do this please subscribe to user-subscribe@mojo.codehaus.org. Thanks!

Emphyrio said...

Sorry, it ate most of the XML.

Maybe I didn't describe it very well. Suppose I were building foo.war; foo.pom declares a dependency on bar.jar; bar.pom declares a dependency on baz.jar. When I build foo.war, Maven automatically pulls in baz.jar even though I didn't declare it in foo.pom.

I'd like to have a similar setup for JS where I only need to declare the direct dependency (bar.js) in the pom, and the plugin will look at the pom for bar.js and automatically pull in its dependency (baz.js).

I'd be happy to post in a forum, but I don't really want to subscribe to a mailing list.

Christopher said...

It should already do what you require...

You should be able to join the mailing list and specify the type of updates you want to receive (digest etc.). If you think there is a bug with the plugin then you can also post a JIRA e.g. http://jira.codehaus.org/browse/MOJO-1698

(you need to prefix issues in the subject with [js-import-plugin] so that they are filtered correctly - once it goes 1.0 it'll get its own JIRA project).