donderdag 1 december 2011

Less ResourceHandler for JSF

My first post about JSF and hopefully this blog will stimulate the community to contribute more LESS related stuff for JSF. The less website can be found here.

LESS
LESS is a dynamic stylesheet language designed by Alexis Sellier. It is influenced by Sass and has influenced newer "SCSS" syntax of Sass, which adapted its CSS like block formatting syntax.
  • LESS provides the following mechanisms: variables, nesting, mixins, operators and functions.
  • LESS can run on the client-side and server-side, with Node.js or Rhino (JavaScript engine).


In order to obtain a fully compatible css file, the less file has to be compiled. As stated above, you can compile the less file server side with Rhino, like Rostislav Hristov e.a from Asual did with a LessServlet, which depends on their LessEngine.

My immediate idea was to bring this to JSF.

LESS and JSF
While you can just add a plain <link> and a <script> into your facelets view as required by less, you won't get the benefits of EL and other JSF related stuff. So, when using h:outputStylesheet in order to get it processed by Faces, it didn't workout as expected.
Less.js will try to compile all links with rel="stylesheet/less", however outputStylesheet will generate a plain rel="stylesheet", and you cannot add this specific attribute either.

Manfred Reim from manorrock suggested the idea of mapping the .less extension to the FacesServlet. This does work when you wrap the content in f:view, however the content-type returned was text/html instead of text/css. While adding the contentType to the view, an IllegalArgumentException was thrown, because 'text/css' is not a valid content-type.
Note, when using a jsp file, setting the content type and some EL expressions, everything works fine.
(This approach can be quit effective when using all kinds of non jsf/html files)

While discussing the above topic on the #jsf irc channel,  a custom resourcehandler was suggested by Matt Benson.

LESS ResourceHandler
The custom resource handler is a ResourceHandlerWrapper, and implements the createResource method.


    @Override
    public Resource createResource(String resourceName, String libraryName) {
        if (resourceName.endsWith(".less")){
            Resource resource = wrapped.createResource(resourceName, libraryName);
            return new LessResource(resource);
        }
        return wrapped.createResource(resourceName, libraryName);
    }


The LessResource does the actual compilation of the less file.

    @Override
    public InputStream getInputStream() throws IOException {
        engine = new LessEngine();
        try {
            String content = convertToString(wrapped.getInputStream()); //returns EL evaluated content
            return new ByteArrayInputStream(engine.compile(content).getBytes("UTF-8"));
        } catch (LessException ex) {
            Logger.getLogger(LessResource.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
            return new ByteArrayInputStream(String.format("/* %s */", ex.getMessage()).getBytes("UTF-8"));
        }
    }


When adding a faces-config and web-fragment file to META-INF, in order to add the resourcehandler and the mime-type, the library is a fully-featured JSF/servlet 3.0 drop-in. Just add the lib to your JSF web application and use <h:outputStylesheet name="somefile.less"/> and off you go!

Server-side versus Client-side
Although it is nice to have a server-side solution, where you can leverage business logic into your css file by using EL, it is now using the scarce resources of the server's CPU. While the beauty of Less is the client-side compilation of less files, at the moment we cannot do that, because of the outputStylesheet restriction in the 'rel' attribute.
I think we need a LessStylesheet component....

Another drawback at the moment, you cannot use @import, because the server-side javascript engine (Rhino) cannot lookup the imported resources. I have to think about the solution, but any idea's are welcome!

Sources can be found here, fork away...