Grails Asset Pipeline - Why it's better

Posted by David Estes on Aug 03, 2013

There are several advantages to using the asset-pipeline instead of the standard grails resources plugin.

  • File dependencies are in the top of your assets. (No Resources.groovy)
  • Assets in plugins become level with your app.
  • On the fly processing in Development mode (No more waiting for reloads)
  • Coffeescript, LESS, and others become first class citizens ( debuggable )
  • Require entire folder trees with one line
  • Better minification (UglifyJs) , and compiling before the WAR is built
  • Faster application startup time
  • Easy extensibility

Before we get too far, it is important to note a few structural differences. The asset-pipeline, does not store it's files in your web-app directory, but rather a new directory generated in grails-app/assets. Within that directory are various subfolder a useful for organizing your assets. It is also nice to note that you can make these sub-folders with whatever names you like and their contents are all still treated as if they are in the root structure (i.e. if I have a file assets/libs/tinymce.js and a file assets/javascripts/application.js. They are still considered as being in the same relative path as each other and application can require tinymce by simply writing //=require tinymce).

File Dependencies

In asset-pipeline, your assets can define their own requirements. The idea being your manifest shouldn't be in a separate file that you have to keep jumping back to to maintain. Asset pipeline is also smart enough to detect doubly defined requirements and discern the correct order needed for your JavaScript or CSS to load correctly without double includes. First lets look at an example JavaScript application.js file:

//=require jquery
//=require ember
//=require handlebars
//=require_self
//=require_tree helpers
//=require_tree models
//=require_tree controllers
//=require_tree views

MyApp = Ember.Application.create({
ready: function() {
  console.log("Application Launched!");
}
});

Notice the '//=' comments at the top of the file? Those are where you would define your "manifest" or list of dependent files. Asset-pipeline (by default) supports 3 types of "require" directives.

  • require_self - controls the location of which this current files contents will be placed.
  • require - requires a file given a relative path or absolute (if not found in relative path, the root is checked amongst all plugins)
  • require_tree - requires all files of similar content type in the specified directory and subdirectories (does not span plugins).

And to include into your gsp file simply use the following tag:

<asset:javascript src="application.js"/>

Pretty nifty right? But what if I require my models yet a particular model needs to ensure another model is loaded first? How do we deal with that? Easy, lets say model Book requires model Author for an association:

/* file name: author.js */
MyApp.Author = MyApp.Model.extend({
id: null,
name: null
});
/* file name: book.js */
//= require author
MyApp.Book = MyApp.Model.extend({
id: null,
name: null,
author: MyApp.Association.BelongsTo(MyApp.Author)
});

Notice in our book.js file we require author. And in our application js we require all models with require_tree. This little require will not cause a double inclusion and will insure a proper load order. This not only works with JavaScript, but with your CSS as well.

Assets in plugins.

Assets in plugins are treated just like assets within your app, barring a few differences. The first, most important difference is that above all, your application assets take priority. If, for example, you have book.js in your assets directory and book.js in a plugins assets directory, your applications copy will take precedence and override the other file. The other difference is that the asset path for a plugin is both the grails-app/assets folder, and the web-app folder. This small difference was done to increase the usability if legacy plugins.

Development Environment

In development mode. Your assets are compiled on the fly (for the case of Coffeescript and other compiled languages as well). You may think this is slow, but recent advancements in Rhino have made this much more performant. This means you don't have to wait for the resources plugin to detect your changes and it becomes instantly available. In development mode, your assets are also automatically included into your layout file individually based on the dependency tree of the file. This preserves order and allows for easy debugging.

Production Environment

In production, the asset-pipeline comes with some grails scripts for preparing for release. It is important that BEFORE creating your War file that you run grails asset-precompile. This script will iterate over your assets and generated compiled, gripped, and cache-digested, minified versions of your files in the web-app/assets folder. This folder is always checked for an existing asset before the asset-pipeline analyzes your source (Note: after you are done generating your War, you may want to delete this folder so you can continue using on the fly development).

Once your application is deployed, your compiled assets are now automatically served by tomcat. Options also exist to automatically copy these assets out to an external path for serving via a CDN or different web server (i.e. nginx).

//Config.groovy
grails {
  assets {
    storagePath = "/mypath/to/assets"
    url = "/assets" // optional URL prefix

Faster Startup times

Because asset-pipeline doesn't preprocess your assets at startup, a significant portion of your load time goes away. It also becomes much quicker to deploy your applications and reduces the amount of downtime your server might have during the restart.

Extensibility

With asset-pipeline it becomes easy to extend and define new asset types. Already here are a list of a few of the available extension plugins:

Conclusion

While this was a rather winded article, by now you should have a good idea of the capabilities and ease of use to the asset pipeline. For more information check out the github or plugin page: