Wednesday, August 1, 2012

Wro4j, Page Load Optimization and Less.js

Wro4j is primary a JavaScript and CSS merge and minimization library. Its original purpose was to speed up the page load. However, its final design made it easy to add integration with LESS, CoffeScript and few other technologies.

Less was introduced in previous article. In short, it is CSS with object oriented inheritance, variables and few other additional features. It is compiled into regular style sheets and served to the browser. Less was written in JavaScript and usually runs in the browser. If you want to run it on the server side, you have to use wro4j or some other integration library.

The post is mostly wro4j tutorial with focus on Less integration. It explains how wro4j works and how to configure it. There is very little about Less and almost everything is about wro4j.

Table Of Contents

Overview

We start with general overview of wro4j and its functionality. This chapter does not go into details, it shows only what is possible.

Licensing information is in the first chapter and the second one contains general overview of what wro4j does. Following sub-chapter lists all available optimizations. Lastly, there are multiple ways how to use wro4j with less.js. All are shown in the last sub-chapter.

Licensing and Code
Wro4j is distributed under Apache License 2.0 and can be found either on Github or on Google Code. Its source code is hosted on Github.

Wro4j has nice and readable source code. If you need something it does not provide, it should be easy to patch it. The project is also very responsive to bug fixes. We found one small bug while writing this article and they fixed it within a day.

What It Does
Wro4j creates groups of JavaScript and CSS resources. All files within one group are merged together and served to the browser as one file.

When the browser requests group for the first time, wro4j:
  • applies pre-processors to each file that belongs to the group,
  • merges all files together,
  • applies post-processors to the merged group,
  • places the end result into the cache,
  • sends the group to the browser.

Following requests for the same group retrieve the group from the cache. Wro4j will not merge the same group twice.

Some pre/post-processors are necessary and merged group does not work correctly without them. Others are optional and perform additional optimization or other useful work. Minimizing library is one such processor, less compiler another. Complete list of available processors in available on wro4j wiki page.

Optimizations
Wro4j offers three basic optimizations:
  • grouping,
  • minimization,
  • entity tag.

First three sections of this sub-chapter describe these three optimizations and the last one contains short note on their effectivity.

Grouping
Grouping lowers number of requests sent to server. If the web page loads each JavaScript or CSS file separately, the server has to handle one request for each resource. If those files are grouped together, then the server has to handle only one request.

Minimization
Minimized style sheets and JavaScript files have smaller download size. Shorter resources are loaded faster.

Minimization strips unnecessary whitespaces and comments. It also shortens long variable and function names.

Entity Tag
Wro4j adds entity tag (Etag) code to headers. Entity tag is computed by the server and sent to browser together with the requested resource. It uniquely identifies the resource and its version.

When the resource content changes, entity tag must change too. Most application use hash of the resource content as etag code.

When the browser needs the same file second time, it adds etag code into the request. The server then compares incoming etag code with etag of current resource version. If they match, the server sends back "304 (Not Modified)" status to the browser.

Otherwise said, whenever the resource changes on the server, the browser will download its new version. If the resource was unchanged, the browser will use cached version.

The full description of the protocol is available here.

Note on Effectivity
We do not know how much each of these optimizations help. We could not find an article or post that would measure how much these things help and we had not done our own experiments.

In any case, minimization and grouping are generally recommended as a best practice.

Less.js With Wro4j
There are three different ways how to use less.js with wro4j:
  • withing framework,
  • compile time,
  • on the fly.

Within Framework
Add wro4j and configure it to use less compiler as one of processors. This option requires the most configuration, but you will be able to use all features and functionality that wro4j offers.

The rest of this post explains how to do it.

Compile Time
Use Maven and wro4j to compile less.js into css at compile time.

A simple step by step tutorial on how to do it is on Nicolas Fränkels blog. In addition to what is written there, Eclipse users will have to do some additional steps described in JBoss Tools article.

The detailed official documentation is on projects google code page.

On the Fly
If you do not care about optimizations and caching, you can use wro4j to compile .less files on the fly.

Such configuration is simpler than what we will show in following chapters, but has also some disadvantages. The biggest one is that the compiler only variant does not support caching. Each .less file is compiled each time it is accessed.

How it Works

The final thing to explain before starting with the configuration is how wro4j works inside. It is not necessary to read this chapter in order to understand the rest of the post.

If you are not interested in wro4j internals, skip to the next chapter.

Wro Filter
Entry point into wro4j is servlet filter. It listens to browser requests and initiates all wro4j work when those requests come. Almost no work is done in the filter, everything is delegated to other classes.

If no group corresponds to uri in request, filter logs an error and forwards the request to other servlet filters.

Wro Manager
Wro manager is the class responsible for all wro4j work. It manages caching, resource locators, applies pre-processors to individual files, merges them, applies post-processors to the result and so on.

It delegates a lot to other classes and requires extensive configuration. It must be supplied several strategy, factory and helper objects. Additionally, wro4j has its own inversion of control framework and wro manager must both configure injection system and be injected by it.

WroManager is very locked down class, all its public methods are final. There is literally nothing to be gained by extending it.

Injector
The inversion of control mini-framework is fairly simple and has no advanced features. The injector injects values into fields marked by @Inject annotation.

Values to be injected are created by object factories which are supplied to the injector in its constructor. Injected value depends on declared class or interface and nothing else. All fields of the same type obtain the value from the same factory.

In addition, if you inject values into an object that implements ObjectDecorator interface, the framework will inject values also into the decorated object. Other than that, there are no conditions or any other advanced logic.

Wro Manager Factory
Wro manager factory creates and configures wro manager. It must implement WroManagerFactory interface.

Although you can create an entirely new implementation of WroManagerFactory, extending the default manager factory BaseWroManagerFactory or one of its sub classes makes more sense. Base factory does all that is necessary, supplies reasonable defaults wherever needed and is designed to be extensible.

Each helper and factory object is created in its own new... method:
  • newModelFactory - factory to create the group model. Default model is configured in a configuration file.
  • newModelTransformers - transformers are applied on created model. the default list contains only wildcard expander. It expands all resources containing * or ** into list of concrete files.
  • newGroupExtractor - extracts group name and requested resource type from the request uri. The default extractor takes file name as a group name and a suffix as a resource type.
  • newProcessorsFactory - factory that creates all pre-processor and post-processors.
  • newHashBuilder - computes hash used for caching and as an etag code.
  • newCacheStrategy - cache is hidden behind this interface.
  • newUriLocatorFactory - uri locator locates resources. It takes uri as an input and returns stream. There are three default uri locators: servlet contex locator, class path locator and url locator.

Base wro manager factory creates injector configured with all objects and factories created by new... methods. The injector is then used on those objects and on all objects created by new...Factory methods.

The end result is that you can use the @Inject annotation to create dependencies between objects created in base wro manager factory and its child factories.

Configuration

Once configured, wro4j requires very little maintenance. It is just there and transparently does whatever it needs to do.

This chapter starts with small sub-chapters on Maven dependencies and on wro4j entry point servlet filter.

It continues with groups configuration. We show how to create groups and add both JavaScript and CSS files into them. Once the groups are configured, wro4j is ready to be used. It does not compile less files yet, but all ordinary files are merged into groups and groups are served to the browser whenever needed.

Finally, the last sub-chapter explains how to replace default pre/post-processors set with a custom one that contains also less compiler.

Maven Dependencies
Wro4j is split into multiple modules. We need two of them: wro4j-core and wro4j-extensions. Wro4j-core contains the minimum wro4j functionality and wro4j-extensions integrates less compiler.

Add wro4j dependencies into pom.xml:
<properties>
  <wro4j.version>1.4.6</wro4j.version>
</properties>

<dependency>
  <groupId>ro.isdc.wro4j</groupId>
  <artifactId>wro4j-core</artifactId>
  <version>${wro4j.version}</version>
</dependency>

<dependency>
  <groupId>ro.isdc.wro4j</groupId>
  <artifactId>wro4j-extensions</artifactId>
  <version>${wro4j.version}</version>
</dependency>

Wro4j-extension manages its dependencies in non-traditional way. Its pom.xml states only the dependencies needed for all its features. Any additional one has to be included by your project.

It does so because it integrates multiple compilers and libraries and any given project is going to use only small subset of them. There is no reason to download and link CoffeScript compiler with all its dependencies if you are not going to use it.

Less compiler processor depends on Rhino, so we have to add it into pom.xml too:
<dependency>
  <groupId>rhino</groupId>
  <artifactId>js</artifactId>
  <version>1.7R2</version>
</dependency>

Filter
The WroFilter servlet filter is the entry point into wro4j. The filter listens to browser requests and initiate all wro4j work when those requests come.

It is configured the same way as any other filter in web.xml file. It listens to all requests for urls matching some pattern. For example, if you want it to place all wro4j groups on /wro/ path, add following into web.xml file:
<filter>
  <filter-name>WebResourceOptimizer</filter-name>
  <filter-class>ro.isdc.wro.http.WroFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>WebResourceOptimizer</filter-name>
  <url-pattern>/wro/*</url-pattern>
</filter-mapping>

The filter should catch only requests for wro4j groups. It assumes that all requests are requests for some merged JavaScript or CSS group. If the assumption turns out to be wrong, wro4j logs an exception and passes the request further. The right file is eventually served, but the log file is polluted by unnecessary exceptions.

Configuring Groups
Groups configuration is called a model. Model knows which files belong to each group and whether they should be minimized.

Wro4j offers four options on how to configure it:
Xml, groovy and JSON configurations differ only in syntax. They offer the same possibilities and work the same way. This post show how to use xml version.

Creating model dynamically from java is naturally more powerful and explained in the last chapter.

Configuration File Structure
The xml configuration file must be named wro.xml and placed in WEB-INF directory.

Its structure is very simple: the root element is groups tag. It contains a list of groups and each group is described by a group tag. Each group tag has a name and contains list of resources that belongs to it.

Following xml contains one group called main. The group contains all css, less and JavaScript files from /resources/ directory and its sub-directories:
<?xml version="1.0" encoding="UTF-8"?>
<groups xmlns="http://www.isdc.ro/wro"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.isdc.ro/wro wro.xsd">

  <group name="main">
    <css>/resources/**.css</css>
    <css>/resources/**.less</css>
    <js>/resources/**.js</js>
  </group>
  
</groups>

The above "everything in one group" configuration assumes that all style sheets are compatible with each other and the group is applicable to every page. Otherwise said, it is impossible to have different css theme for some pages. It also assumes that all JavaScripts are compatible with each and needed on every page.

If this is the case, then you have to split style sheets and JavaScripts into multiple groups:
<?xml version="1.0" encoding="UTF-8"?>
<groups xmlns="http://www.isdc.ro/wro"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.isdc.ro/wro wro.xsd">

  <group name="theme1">
    <css>/resources/common/**.css</css>
    <css>/resources/common/**.less</css>
    <js>/resources/common/**.js</js>
    <css>/resources/theme1/**.css</css>
    <css>/resources/theme1/**.less</css>
    <js>/resources/theme1/**.js</js>
  </group>

  <group name="theme2">
    <css>/resources/common/**.css</css>
    <css>/resources/common/**.less</css>
    <js>/resources/common/**.js</js>
    <css>/resources/theme2/**.css</css>
    <css>/resources/theme2/**.less</css>
    <js>/resources/theme2/**.js</js>
  </group>
  
</groups>

Js and Css Tags
Resources are listed using js and css tags. Whatever is referenced by js tag is assumed to be JavaScript and whatever is referenced by css tag is assumed to be CSS file.

It is important to keep this distinction clear. JavaScript and CSS files from the same group are served separately and use different pre and post processors.

Both js and css tags can reference resources on classpath, file system, relative to the servlet context or by url. It is also possible to extend them and add new resource locations (database, ...), but such thing is out of scope of this post.
<css>classpath:org/somewhere/style.css</css>
<css>file:src/main/webapp/resources/hangman/style.css</css>
<css>http://localhost:8080/wicket-wro/resources/hangman/style.css</css>

They both support * wildcard and ** deep wildcard. Wildcard matches only files in specified directory. It does not go into sub-directories. Deep wildcard matches also files in all sub-directories:
<group name="deep_wildcard_goes_into_subdirectories">
  <css>/resources/**.css</css>
</group>
<group name="wildcard_has_to_list_all_subdirectories">
  <css>/resources/*.css</css>
  <css>/resources/hangman/*.css</css>
  <css>/resources/home/*.css</css>
</group>

All resources in the group are minimized by default. If you wish to turn off minimization for some resource, add minimize="false" attribute to js or css tag. Of course, the minimize attribute works only if the minimizing processor is configured as a pre-processor (more on that later).
<css minimize="false">/resources/**.css</css>

Note: Both js and css tags in the current version 1.4.6 have a bug. Neither of them works correctly if it references the project root. For example, <css>/**.css</css> does not work. The workaround is to place all resources in some sub-directory and reference that one: <css>/resources/**.css</css>. The bug was already fixed and the next released version will not have this limitation.

Group References
Any group can reference resources from other groups using the group-ref tag.

It is used to avoid repetition and place common resources into one group. For example, previous multi-group configuration can be rewritten into following form:
<?xml version="1.0" encoding="UTF-8"?>
<groups xmlns="http://www.isdc.ro/wro"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.isdc.ro/wro wro.xsd">

  <group name="common-resources">
    <css>/resources/common/**.css</css>
    <css>/resources/common/**.less</css>
    <js>/resources/common/**.js</js>
  </group>

  <group name="theme1">
    <group-ref>common-resources</group-ref>
    <css>/resources/theme1/**.css</css>
    <css>/resources/theme1/**.less</css>
    <js>/resources/theme1/**.js</js>
  </group>

  <group name="theme2">
    <group-ref>common-resources</group-ref>
    <css>/resources/theme2/**.css</css>
    <css>/resources/theme2/**.less</css>
    <js>/resources/theme2/**.js</js>
  </group>
  
</groups>

Using Groups
Configured groups are used the same way as any other JavaScript or css resource.

Assuming that the wro filter catches every request for /wro/* and that you have a group named main, you can include all its css resources using:
<link rel="stylesheet" type="text/css" href="wro/main.css">

Similarly, you can include all its js resources the same way as you would an ordinary JavaScript:
<script type="text/javascript" src="wro/main.js"></script>

Note: unless configured otherwise, wro4j takes requested file name as a group name and a suffix as a resource type. It ignores the path and recognizes only two possible suffixes: .css and .js. Therefore, following snippet loads the same group as previous two:
<script type="text/javascript" src="wro/something/main.js"></script>
<link rel="stylesheet" type="text/css" href="wro/something/main.css">

Processors
Wro4j applies pre-processors to each file before merging and post-processors to the merged group. As we have not explicitly configured them yet, our project is using the default set.

The list of processors is created in wro manager factory. The default factory supplies reasonable list of default processors, but it is unable to create less compiler. Therefore, we have to:
  • replace the default factory with something more flexible,
  • configure new factory to use all necessary pre/post-processors,
  • configure new factory to use less compiler.

First section shows how to replace the default factory and second chapter explains how to configure it. Remaining sections go through all used processors and explain what they do and why we had to use them.

Replace Manager Factory
First we have to choose the right manager factory. Manager factory is a class that implements WroManagerFactory interface and wro4j has a lot of them. Most of manager factory implementations serve unit tests, Maven integration, command line integration or other "helper" cases.

Four manager factories are designed to be used in web application:
  • DefaultWroManagerFactory - used if nothing else is configured.
  • CustomWroManagerFactory - meant to be extended. Extend it if no standard factory produces what you need.
  • ConfigurableWroManagerFactory - able to create all processors from basic wro4j package. Partially configurable in wro.properties file.
  • ExtensionsConfigurableWroManagerFactory - able to create all processors from wro4j-extensions package. Partially configurable in wro.properties file.

We will use ExtensionsConfigurableWroManagerFactory because it supports all processors we need and is easy to configure.

Create wro.properties file and place it to the WEB-INF directory. Use managerFactoryClassName property to configure wro manager factory class name:
managerFactoryClassName=ro.isdc.wro.extensions.manager.ExtensionsConfigurableWroManagerFactory

Configure Manager Factory
Extension configurable manager factory has three configurable properties:
  • uriLocators - uri locators locates resources. They take uri as an input and returns stream. This property has reasonable default value, so we will leave it as it is.
  • preProcessors - list of pre-processors. It is empty by default.
  • postProcessors - list of post-processors. It is empty by default.

Neither merged .css nor merged .js files work correctly with empty pre-processors list. Simplest working configuration requires three pre-processors: cssUrlRewriting, cssImport and semicolonAppender.

Post-processors are not necessary, but some of them are very useful. We decided to use three post-processors: lessCss, jsMin and cssMin.

Final wro.properties file:
managerFactoryClassName=ro.isdc.wro.extensions.manager.ExtensionsConfigurableWroManagerFactory
preProcessors=cssUrlRewriting,cssImport,semicolonAppender
postProcessors=lessCss,jsMin,cssMin

Processors are applied in the configured order. Each processor knows whether it should be applied to JavaScript, css or both files. You do not have to worry about wrong type of processor being applied to wrong type of file.

Relative References in CSS
Loading css file from the group instead of its original location breaks relative references. Unless import statements and image locations are rewritten, @import statement or referenced images do not work anymore.

Resources references rewriting is done in cssUrlRewriting and cssImport pre-processors. Their order does matter, the css url rewriting processor must go first.

The cssUrlRewriting rewrites relative references to images so they work from the new location. The cssImport does two things:
  • adds imported .css files into the group,
  • removes the import statement.

Use both cssUrlRewriting and cssImport as pre-processors:
preProcessors=cssUrlRewriting,cssImport

Missing ; in .js Files
JavaScript file may miss last semicolon and still be valid JavaScript file. However, once you merge those files together, the missing semicolon may cause a lot of problems.

Use semicolonAppender pre-processor to add missing last ; into JavaScript files.

Add semicolonAppender to pre-processors list:
preProcessors=cssUrlRewriting,cssImport,semicolonAppender

Minimization
Strictly speaking, this is not necessary. However, since we already added wro4j into the project and configured it, we can take advantage of its minimization abilities as well.

Use cssMin to minimize css files and jsMin to minimize JavaScript files. Both are able to work as both pre-processors and post-processors.
postProcessors=jsMin,cssMin

Note: jsMin is not the only JavaScript minimizer available in wro4j. Wro4j-extensions contains also Google closure compiler, YUI compression utilities, Dojo Shrinksafe utility and other popular minimizers. All minimizers are listed in the processors list.

Less Compiler
Less compiler is implemented in LessCssProcessor. It can act as both pre-processor or post-processor. If you use it as a pre-processor the @import statement does not work, so it is better to ignore that option and use it as a post-processor only.

Do not worry about JavaScript files in the same group, LessCssProcessor does not modify them.

Add less compiler to the list of post processors. It make more sense to compile first and minimize later:
postProcessors=lessCss,jsMin,cssMin

Dynamic Model

Having files grouped together lowers number of requests, but also adds some additional hustle. The more fine grained groups are, the more likely they break when you add, move, rename or remove files.

If the model is predictable, it can be build dynamically in Java code. If it is done right, wro4j acts transparently to the rest of the application and requires minimal maintennance.

This chapter shows how to create very simple dynamic model: each .css and .less file have its own group and is processed separately. As an additional bonus, it fixes the bug mentioned above.

Sample Project
We took Apache Wicket Hangman example project from the Apache repository and modified it to use wro4j with dynamic model.

Use the StartExamples class to run the project. It will configure and start jetty server with hangman application running on it. The application is then available at http://localhost:8080/wicket-wro/hangman/ url.

The sample project is available on Github.

Filter
First, we have to configure WroFilter to catch all requests for .css or .less files. We will not assume that they are all located in some special location.

Add following into web.xml file:
<filter>
 <filter-name>WebResourceOptimizer</filter-name>
 <filter-class>ro.isdc.wro.http.WroFilter</filter-class>
</filter>
<filter-mapping>
 <filter-name>WebResourceOptimizer</filter-name>
 <url-pattern>*.less</url-pattern>
</filter-mapping>
<filter-mapping>
 <filter-name>WebResourceOptimizer</filter-name>
 <url-pattern>*.css</url-pattern>
</filter-mapping>

The Model
Wro4j uses two steps algorithm to create the model:
  • model factory creates initial model,
  • initial model is transformed into final model using model transformers.

The default model factory creates initial model that corresponds exactly to the content of wro.xml file. Wro4j then applies its only default model transformer to it. The transformer is called wildcard model transformer and expands all resources containing * or ** into list of concrete file resources.

We have to create a model that contains all .css and .less files, so it makes sense to take advantage of the wilcard model transformer. Our initial model has only one group which uses deep wilcard to reference all those files. It corresponds to following xml file:
<group name="fake">
  <css>/**.css</css>
  <css>/**.less</css>
</group>

Then we use wildcard model transformer to replace wildcards with list of concrete files. Once we have all files in one group, we will use custom model transformer to move each file into separate group.

Initial Model
The model must implement WroModel interface and is created by an instance of WroModelFactory. The model contains a list of groups Group and each group contains list of resources Resource:
public class ConfigurationFreeModelFactory implements WroModelFactory {
  
  public WroModel create() {
    WroModel result = new WroModel();
    result.addGroup(createAllResourcesGroup());
    return result;
  }

  private Group createAllResourcesGroup() {
    Resource allLess = new Resource();
    allLess.setType(ResourceType.CSS);
    allLess.setUri("/**.less");

    Resource allCss = new Resource();
    allCss.setType(ResourceType.CSS);
    allCss.setUri("/**.css");

    Group group = new Group("fake");
    group.addResource(allCss);
    group.addResource(allLess);
    return group;
  }

  public void destroy() {
  }

}

Model Transformers
We use two model transformers:
  • wildcard model transformer to expand wildcards,
  • custom resources to groups model transformer to create a separate group for each file.

Wildcard model transformer expands wildcards into list of resources. The default wildcard model transformer has a bug, so we will replace it with a fixed one.

Find the fixed model transformer in our demo project on Github. Since the bug is already fixed in wro4j trunk, we will not show the code here. The solution is only temporary and fixed wildcard model transformer will not be needed after the next wro4j release.

Second model transformer creates a separate group for each resource. Each group must have unique name, so we used resource uri as the group name. For example, a style sheet named main.css and located in resources directory is placed into group named /resources/main.css:
public class ResourcesToGroupsModelTransformer implements Transformer<WroModel> {

  public WroModel transform(WroModel input) throws Exception {
    WroModel result = new WroModel();
    for (Group group : input.getGroups()) {
      for (Resource resource : group.getResources()) {
        Group resourceGroup = toGroup(resource);
        result.addGroup(resourceGroup);
      }
    }
    return result;
  }

  private Group toGroup(Resource resource) {
    Group resourceGroup = new Group(resource.getUri());
    resourceGroup.addResource(resource);
    return resourceGroup;
  }

}

Group Extractor
Group extractor extracts group name and resource type out of incoming request. It also returns whether the group should be minimized and whether it is possible to compose group name and resource type back to the original uri.

Default group extractor takes requested file name as the group name and a suffix as the resource type. It recognizes only two possible suffixes: .css and .js. Our dynamic model uses whole uri as the group name and contains also .less files. As a result, the default wro configuration is unable to find any of our generated groups.

For example, the request for context path/resource/main.css would be resolved as a request for all style sheets inside the main group. As our group is named /resource/main.css, it would not be found. Additionally, we need it to be recognize .less files as a style sheets, but the default implementation discards them as unknown resource types.

A group extractor must implement GroupExtractor interface. Its full implementation is available on Github. Most of it is trivial, so following example contains only the most important part:
public class ConfigurationFreeGroupExtractor implements GroupExtractor {

  private final DefaultGroupExtractor defaultExtractor = 
      new DefaultGroupExtractor();

  /**
   * Everything that follows context path is considered a group name.
   */
  public String getGroupName(HttpServletRequest request) {
    String contextPath = request.getContextPath();
    String uri = getUri(request);
    if (uri.startsWith(contextPath))
      uri = uri.substring(contextPath.length());
    
    return uri;
  }

  /**
   * If the default extractor is unable to find the resource type,
   * check whether it is a .less file. Less files are considered 
   * style sheets.
   */
  public ResourceType getResourceType(HttpServletRequest request) {
    ResourceType resourceType = 
        defaultExtractor.getResourceType(request);
    //if the default extractor could not find the type
    //check whether it is .less file
    if (resourceType==null && isLessFile(request)) {
      resourceType = ResourceType.CSS;
    }
    return resourceType;
  }

  private boolean isLessFile(HttpServletRequest request) {
    return request.getRequestURI().toUpperCase().endsWith(".LESS");
  }

  ... 

}

Wro Manager Factory
We have custom model factory, custom group extractor and custom model transformer. The last thing we need is to create and configure custom wro manager factory that produces our new objects.

All wro manager factories are designed in very similar way and it does not matter too much which one we extend. As we have ExtensionsConfigurableWroManagerFactory already configured with needed processors, we will extend this one and override three its methods:
  • newModelFactory - creates model factory,
  • newModelTransformers - creates model transformers,
  • newGroupExtractor - creates group extractor.

New factory is located in org.meri.wro4j.nogroups package:
public class ConfigurationFreeManagerFactory extends ExtensionsConfigurableWroManagerFactory {

  @Override
  protected GroupExtractor newGroupExtractor() {
    return new ConfigurationFreeGroupExtractor();
  }

  @Override
  protected WroModelFactory newModelFactory() {
    return new ConfigurationFreeModelFactory();
  }

  @Override
  protected List<Transformer<WroModel>> newModelTransformers() {
    //The super class store the list of model transformers in a private 
    //property. We need both modify that property and return the list of 
    //correct transformers.
    List<Transformer<WroModel>> modelTransformers = 
        super.newModelTransformers();
    //replace default WildcardExpanderModelTransformer with 
    //FixedWildcardExpanderModelTransformer
    modelTransformers.clear();
    addModelTransformer(new FixedWildcardExpanderModelTransformer());
    addModelTransformer(new ResourcesToGroupsModelTransformer());
    return modelTransformers;
  }
}

Wro.properties configuration:
managerFactoryClassName=org.meri.wro4j.nogroups.ConfigurationFreeManagerFactory
preProcessors=cssUrlRewriting,cssImport,semicolonAppender
postProcessors=lessCss,jsMin,cssMin

End

Although wro4j configuration requires some work, easy access to optimizations and technologies like less or CoffeScript may be worth it.

15 comments:

Alex said...

Thanks Meri for this blog post. It describes everything in a nice and clear way.

Unknown said...

Good work!

lesscss said...

Very Nice to read this one ...

lesscss said...

Nice code

hemcoined said...

Great work!
Check Valve Distributor

carlos said...

wro4j is nice, but i have a conflict with jodaTime (maven dependency).

imaginedesign said...

I started setting this up as part of our build process. I have been using grunt etc to handle a lot this, but looking at a server filter solution.

I've been trying to get the properties file setup to work with less files. In particular a massive (bootstrap 3)less file - which includes many imports. The file output for the wro/bs.less comes out as a blank file. Any ideas?

Alex said...

@imaginedesign post your question on the wro4j mailing list or open an issue with detailed description.

Anonymous said...

Great Post, I love to read articles that are informative and actually have good content. Thank you for sharing your experiences and I look forward to reading more.
website design

bhashit said...

Excellent stuff... Almost everything one might need to get started with wro.

Anonymous said...

How to do versioning control of minified js and css file?

Melbourne SEO Service said...

I found this article easy to understand and very helpful. Can’t wait to see the other posts. Thank you for sharing!


admin said...

Unique and perfect stuff you have posted here due to your great efforts, I a am very happy and want to bookmark your stuff for future read.
best local seo services

Unknown said...

This is an excellent post I seen thanks to share it. It is really what I wanted to see hope in future you will continue for sharing such a excellent post. web development company

Oncasinosite Net said...

I admire what you have done here. I like the part where you say you are doing this to give back but I would assume by all the comments that this is working for you as well. 바카라사이트

Post a Comment