Using wro4j to Easily Switch Between Minified and Debuggable JavaScript/CSS

by Clint on May 31, 2011

I recently did some work on a large web application that had several .js and .css files. Per industry best practice, I wanted to use something like YUI Compressor or Google Closure Compiler to merge and minify all the JavaScript and CSS into a single .js file and a single .css file. Also, I wanted an easy way to switch from these optimized files–which are hard to read–to the original files during development (or to debug an issue in production).

In this case, the JS/CSS needed to be minified at build-time. The project was built via Maven, so it made sense to do this with a Maven project. After a lot of research, I chose a plugin called wro4j because it allows you to use an external XML file to configure exactly which files should be minified/optimized. And by making this XML file part of the website itself, you can use the information it contains (i.e., the list of individuals .js and .css files) to dynamically switch from the compressed files to the “raw” ones. The main benefit? The list of .js and .css files exists in only one place–the XML config file. Here’s what it looks like:

 
      /css/reset.css
   /css/master.css
   /css/typography.css

      /js/lib/swfaddress/swfaddress.js
   /js/constants.js
   /js/util.js
   ...
 

As I said, this configuration is used by the wro4j plugin when it minifies/compresses/optimizes the code at build-time. I won’t go into the details of how to configure wro4j here; see the wro4j website for info on that.

So assuming you have wro4j configured to generate myapp-all-compressed.js and myapp-all-compressed.css, the next thing you’ll do is modify your HTML doc to use those files:

  
    
    
  
  ...

So how can we easily switch from these hard-to-read files to the “original” files? We’ll use JavaScript to download the aforementioned XML config file (resources.xml), parse it, and dynamically add

  
  
    
    
    
  
    
    
    
  
  ...

As you can see, navigating to http://yoursite.com/ results in the “optimized for production” files being used, but something like http://yoursite.com/?debug causes the browser to instead download loadIndividualJsCssFiles.js and call myapp.util.loadIndividualJsCssFiles() with the path to resources.xml.

Finally, let’s take a look inside loadIndividualJsCssFiles.js. At a high level, it extracts the list of .js/.css files from resources.xml and dynamically adds

if( myapp === undefined ) { myapp = {}; }
if( myapp.util === undefined) { myapp.util = {}; }

/**
 * Use this function to dynamically load a single CSS file.
 *
 * @param {String} path - URL path to the .css file that should be loaded
 */
myapp.util.loadStylesheetFile = function(path) {
 var linkElement = document.createElement('link');
 linkElement.setAttribute("type", "text/css");
 linkElement.setAttribute("rel", "stylesheet");
 linkElement.setAttribute("href", path);

 // Append the new  element to the end of . The browser should
 // start downloading it immediately.
 myapp.console.info('Loading '+ path);
 document.getElementsByTagName("head")[0].appendChild(linkElement);
};

/**
 * Use this function to dynamically load a single JavaScript file.
 *
 * @param {String} path - URL path to the .js file that should be loaded
 * @param {Object} callbackFcn - (optional) A function that will be called if/when the script finishes loading
 */
myapp.util.loadJavaScriptFile = function(path, callbackFcn) {
 var elementOnLoadFcn,
     scriptElement = document.createElement('script');

 scriptElement.setAttribute("type", "text/javascript");

 if( callbackFcn != undefined && callbackFcn != null ) {
   // Create a function that will be used as the value for the 

The end result is that you keep the list of .js/.css files in one place (resources.xml)–you don’t have to keep updating your .jsp or pom.xml file each time you need to add or remove .js/.css files. The loadJavaScriptFiles() function will ensure those files get picked up the next time you refresh the browser with the ‘debug’ URL param, and wro4j will similarly include those files in myapp-all-compressed.js/myapp-all-compressed.css the next time a build is done.

Clint Harris is an independent software consultant living in Brooklyn, New York. He can be contacted directly at ten.sirrahtnilc@tnilc.
  • Rafael Franco

    Seems good! How do you access the wro.ml (or resources.xml) as you say since it must be in the WEB-INF root folder?

  • John Gordon

    Thanks for posting this, but I’m seeing three issues:

    1. Per Rafael above, the wro.xml file isn’t readily accessible to the browser as it is in WEB-INF. I got around that temporarily by copying my wro.xml to my web app’s root.
    2. myapp threw an error in the js load script as it was undefined. I took out the two lines at the top and just manually created them, assuming they hadn’t been before
    3. wildcards are not supported in the wro.xml file. I got the following errors in the browser console:

    GET http://localhost:8080/scripts/dvm/client/**.js 404 (Not Found)
    Deaggregator.js:61Loading /scripts/dvm/order/**.js
    GET http://localhost:8080/scripts/dvm/order/**.js 404 (Not Found)
    Deaggregator.js:61Loading /scripts/dvm/product/common/*.js
    GET http://localhost:8080/scripts/dvm/product/common/*.js 404 (Not Found)
    Deaggregator.js:61Loading /scripts/dvm/product/*.js
    GET http://localhost:8080/scripts/dvm/product/*.js 404 (Not Found)
    Deaggregator.js:61Loading /scripts/dvm/report/**.js
    GET http://localhost:8080/scripts/dvm/report/**.js 404 (Not Found)
    Deaggregator.js:61Loading /scripts/dvm/rxlabel/**.js
    GET http://localhost:8080/scripts/dvm/rxlabel/**.js 404 (Not Found)
    Deaggregator.js:61Loading /scripts/dvm/practice/**.js
    GET http://localhost:8080/scripts/dvm/practice/**.js 404 (Not Found)

    Thanks for your efforts, but I’m not sure a client-side de-aggregator solution will fit the bill.

  • Brian Chapman

    It looks like wro4j now supports json as a configuration file option. This would make implementing loadIndividualJsCssFiles much easier.

    Commenting on the above 2 comments:

    You can pass the wroFile configuration option in to relocate the wro.xml (or wro.json) file.

    Wildcards are supported in the wro.xml file, per the docs (this may or may not be new since the above comment).