Minify all your JavaScript and Stylsheets automatically with YUI Compressor and a Servlet Filter

In this article I will explain how to minify all your .js and .css files automatically with a servlet filter. This means you only have to define a servlet filter in your web.xml and everything works!

In the project I’m currently working on we have some pretty large JavaScripts and CSS files. Those files also contain comments that the users don’t need to use the application. So I wanted to be able to remove unnecessary whitespace, line breaks and comments. When I read an article on Ajaxian about the YUI Compressor library I got curious. I assumed the library was written in C or another language that doesn’t start with J and ends with ava. But I got lucky, it was a Java library!

....

I hate to do thing manually or create setup files, so I came up with a solution: a Servlet Filter for .css and .js files. That idea sounds so simple that there has to be someone who already did that. I searched for a while and the only thing I found was someone who said he was going to make it and it seems he wants to see some money for it! Well that’s not so cool. YUI Compressor is free and when you make a simple thing like a servlet filter you should donate it to the community. I’m sure someone already created a servlet filter, but that person should’ve posted the article on DZone or javablogs so other people know it exists.

But enough talking let me show you what I did. I won’t describe every little detail, I only want to give you an idea what I did. I will provide the source code so you can see what’s done exactly and if you have any questions about that I will answer them (in the comments of this article).

Servlet Filter

The first step is setting up the servlet filter and find out what the input and output of the filter is. A Servlet Filter is nothing more than a class that implements the javax.servlet.Filter interface.
A servlet filter consists of three methods:

public void init(FilterConfig filterConfig)
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
public void destroy()

The init method is the place where you can read input parameters and setup the filter. This method acts as a setter for a private filterConfig field and reads the filter init parameters. The filter init parameters are the same parameters you can pass to the YUI Compressor library. I choose not to use all the parameters, just a subset with the most important ones.

The doFilter() method checks whether the compressed file already is in our cache and writes it to the ServletOutputStream. The input for the YUI Compressor is a java.io.Reader object. We can get a Reader object with the following code:

String requestURI = request.getRequestURI();
InputStream inputStream = context.getResourceAsStream(requestURI);
InputStreamReader isr = new InputStreamReader(inputStream);

The isr object can be passed to the compressor. The compressor writes the output to the cache (with a StringWriter object). When a file is found in the cache it’s written to the ServletOutputStream:

ServletOutputStream servletOutputStream = response.getOutputStream();

The servletOutputStream is a parameter of the Compressor.

The destroy() method is empty, because we have no sepcial things that need to be cleaned up (like open files or connections to external resources).

YUI Compressor

The second step was understanding the YUI Compressor.  I used the 2.1.2 version, but everything I’m about to tell should work with older and newer versions. In the download on the page the source code is included. The YUI Compressor class is the class we will use as a reference for the Filter, it contains all the important information we need.
The primary goal is compressing some input. The input the YUIC needs is a java.io.Reader object and an ErrorReporter object.

JavaScriptCompressor compressor = new JavaScriptCompressor(in, new ErrorReporter() { ... });

The ErrorReporter object is something I created myself. My class is called CompressorFilterErrorReporter and it implements the ErrorReporter interface. I will skip this class, it’s quite straightforward what needs to be done and you can look it up in the source code. You only need to know that it prints some error messages.
The last step of the compressor is invoking the compress method. You have to pass the command line parameters and the Write you want to write to. Because we make use of a cache we write to a String with a StringWriter.

The CSS compressor works basically the same as the JavaScriptCompressor, but needs fewer parameters.

Caching

It turned out the compressor was pretty slow (on my laptop it almost took 1 second to compress the jQuery library). Of course it’s a bit useless to compress a script everytime a request is made so had to introduce caching anyway. I use a Hashtable (yes, with a lower case t)  to cache the scripts, the cache won’t expire, there’s no round robin etc. You usally won’t have hundreds of scripts and stylesheets in the cache so a normal Hashtable will be enough.
Instead of writing directly to the ServletOutputStream I now write to a String and put this String with the Request URI in the Hashtable. It’s a simple caching mechanism, but it works pretty good.
The first time the scripts are compressed it might take a little longer to load the page but every new request is pretty fast (maybe even faster than a normal request because this one doesn’t need to be read from disk)

Configuration
You have to put the filter in your web.xml. The simplest configuration looks like this:

<filter>
    <display-name>Yahoo Compressor Filter</display-name>
    <filter-name>CompressorFilter</filter-name>
    <filter-class>nl.amis.filter.CompressorFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>CompressorFilter</filter-name>
    <url-pattern>*.js</url-pattern>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>

<filter-mapping>
    <filter-name>CompressorFilter</filter-name>
    <url-pattern>*.css</url-pattern>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>

I decided to work with the default values of YUI Compressor. You only need parameters when you don’t want to use the defaults. When you want to use the parameters you have to put them between </filter-class> and </filter>

    <init-param>
        <param-name>preserve-semi</param-name>
        <param-value>true</param-value>
    </init-param>

Is used to preserve all the semi colons. Other parameters you can use are line-break, warn and nomunge. Consult the YUI Compressor to see what these parameters do.

Conclusion and download

Click here to download the files. You have to download the YUI Compressor yourself. I also make use of Log4J. When you don’t want to use Log4j you just have to replace it with your own logging library or System.out. There are only 5 logging statements, so it shouldn’t be too much work.
Many thanks to the guy(s) tha
t created the YUI Compressor, i
t’s easy to use and does a great job.
You’re free to use the source code in your application, just don’t remove my name from the Javadoc ๐Ÿ˜‰
I’d really appreciate some feedback on this piece of code, it probably can be improved.

Sources

http://www.julienlecomte.net/blog/2007/08/13/introducing-the-yui-compressor/
http://www.julienlecomte.net/yuicompressor/

9 Comments

  1. ELahti May 20, 2009
  2. Jordi Hernandez February 5, 2008
  3. sungkwon.eom January 17, 2008
  4. Steven Jardine November 2, 2007
  5. Jeroen van Wilgenburg September 11, 2007
  6. Jeroen van Wilgenburg September 11, 2007
  7. Jacob Hookom September 10, 2007
  8. Mathias Bogaert September 10, 2007
  9. Patrick Wolf September 9, 2007