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.
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.
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.
When the browser requests group for the first time, wro4j:
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.
First three sections of this sub-chapter describe these three optimizations and the last one contains short note on their effectivity.
Minimization strips unnecessary whitespaces and comments. It also shortens long variable and function names.
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.
In any case, minimization and grouping are generally recommended as a best practice.
The rest of this post explains how to do it.
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.
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.
If you are not interested in wro4j internals, skip to the next chapter.
If no group corresponds to uri in request, filter logs an error and forwards the request to other servlet filters.
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.
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
Although you can create an entirely new implementation of
Each helper and factory object is created in its own
Base wro manager factory creates injector configured with all objects and factories created by
The end result is that you can use the
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.
Add wro4j dependencies into pom.xml:
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:
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
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.
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.
Its structure is very simple: the root element is
Following xml contains one group called main. The group contains all css, less and JavaScript files from
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
Click to expand the 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 implementWroManagerFactory
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
TheWroFilter
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:
- wro.xml xml file,
- wro.groovy groovy file,
- JSON configuration - requires additional dependency,
- use java to dynamically create groups.
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 usingjs
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 thegroup-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 implementsWroManagerFactory
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 inLessCssProcessor
. 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 implementWroModel
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
16 comments:
Thanks Meri for this blog post. It describes everything in a nice and clear way.
Good work!
Very Nice to read this one ...
Nice code
Great work!
Check Valve Distributor
wro4j is nice, but i have a conflict with jodaTime (maven dependency).
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?
@imaginedesign post your question on the wro4j mailing list or open an issue with detailed description.
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
Excellent stuff... Almost everything one might need to get started with wro.
How to do versioning control of minified js and css file?
I found this article easy to understand and very helpful. Can’t wait to see the other posts. Thank you for sharing!
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
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
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. 바카라사이트
This article on mental health app development is a comprehensive guide for developers looking to create innovative solutions for those struggling with mental health issues. It offers valuable insights into the challenges faced by individuals and how technology can be used to provide support and therapy. A must-read for anyone in the app development industry.
Post a Comment