Java library for efficient CSS and JavaScript rendering
This article describes a method for transferring JavaScript and CSS by combining resources, with their subsequent minimization and compression, using the small Combinatorius Java library, which allows you to speed up and simplify the transfer of content.
Demo: combinatorius.dkiriusin.com
GitHub: github.com/deniskiriusin/combinatorius
The speed of loading a web page depends on many factors, some of which Web developers need to know.
We will not discuss the speed provided by the Internet provider, DNS settings or the geographical location of the resource, but focus on the HTTP protocol and those methods that we can use to accelerate the transfer of CSS and JavaScript content.
So, the main factors affecting the page loading speed are:
- The size of the transmitted content
- The number of HTTP requests
Accordingly, our goal is to reduce the size of the transmitted content and the number of requests to the server to a minimum.
Consider what happens when the page loads. Modern Internet resources with a rich user interface send dozens and sometimes hundreds of HTTP requests to download content. Many of them are in CSS and JavaScript. The total weight of the transmitted CSS and JavaScript is usually several hundred kilobytes or more. You can reduce the amount of transmitted content by minimizing and compressing it.
Minimization is a process whose task is to reduce the script while maintaining its functionality. This is achieved by removing comments from the script, spaces, as well as reducing the names of variables.
The library fully supports YUI Compressorto minimize CSS / JavaScript and gzip for data compression, which can reduce the total weight of the transmitted CSS and JavaScript at times. By default, the library does not minimize resources whose names contain the suffix ".min.".
You can change the regular expression in
Since data compression is a resource-intensive process, the final data is cached both on the server and client side, and is provided directly from the cache on subsequent requests. If CSS and JavaScript change, data is minimized, compressed and cached again.
We need the client to send only two requests, regardless of the number of CSS and JavaScript resources on the page. One per CSS and JavaScript respectively. Let's see what the benefits of this approach are.
As RFC 2616 states, an HTTP request must conform to the format:
In real life, it will look something like this:
Based on the same RFC 2616, the HTTP response format is as follows:
Chrome DevTools will show us something like:
The size of HTTP headers can vary from 200 bytes to 2KB or more, taking into account Cookies.
Funny, but most of all you have to pay for the little things. For example, creating a page with ten static resources, each one byte in size, the browser will have to send and receive in response a few kilobytes of HTTP headers alone to download 10 bytes of useful data.
But the main problem is not even that, but that - the requests are slow. Modern browsers are multi-threaded and try their best, but somehow for almost every HTTP request you need to determine the DNS, create a connection to the server, then SSL handshake, in the case of HTTPS ... And only after that we can get an HTTP response from the server. All this takes time, and the more requests, the more time it takes to load the page.
Fortunately, HTTP headers can be extremely useful, and the library skillfully arranges them to maximize the speed of loading CSS and JavaScript.
The Cache-Control header directives determine who can cache the HTTP response, under what conditions and for how long. It’s best not to send the request at all, but to save a copy of the response in the browser’s cache and take it from there so as not to communicate with the server. This eliminates the need to pay for data transfer over the network.
So the max-age directive determines the maximum time in seconds during which the received response can be reused from the browser cache. The library caches data for one year by default.
You can change the configuration at
This header is essentially an analogue of Cache-Control, which has supplanted it in HTTP / 1.1. Expires also determines how long data can be cached on the client side. The library sets Expires one year ahead by default.
You can change the configuration at
Provides cache validation and allows the client to send a conditional request. This allows the cache to be more efficient, since the web server does not need to send a full response if the content has not changed. The library uses ETag similarly to using fingerprints. For example, there is no need to change the names of CSS and JavaScript resources, after making changes to them, with long caching. The library automatically recognizes changes made to CSS and JavaScript. Data is automatically minimized if necessary, compressed, cached and delivered to the client with all the necessary headers.
The library is accessible from a central repository.
Register servlet c
, from now on, all requests to
All that is needed next is to create a file
Using Tomcat as an example, you can achieve this by changing
Before:
After:
Accordingly, create a directory:
And copy to it
The library works with CSS and JavaScript resources in
Resources matching a regular expression are
Support for CSS themes is also provided. CSS theme is a
It is possible to connect additional resources that are not included in
For simplicity and reliability, I recommend using the JSP tag to generate URLs. One tag per CSS and JavaScript respectively. Mandatory attributes are
Using a JSP tag has one important advantage. The tag automatically subscribes resources by adding a version at the end of the URL to solve cache cache busting problems.
Read links:
developer.yahoo.com/performance/rules.html
developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/?hl=en
Demo: combinatorius.dkiriusin.com
GitHub: github.com/deniskiriusin/combinatorius
Key library features
- Combining JavaScript and CSS into one JavaScript and CSS resource, respectively, to reduce the number of HTTP requests.
- Local caching of generated data to improve response time.
- The correct Expires and Cache-Control HTTP headers to help the browser with conditional requests.
- ETag support for determining the correspondence between the browser cache and the data on the server.
- Gzip compression to reduce the size of the HTTP response.
- Support for YUI Compressor.
- Support for versions of transferred resources (fingerprinting & static resources versioning).
- Support for CSS themes via URL or Cookies options.
- Simple configuration.
Brief analysis
The speed of loading a web page depends on many factors, some of which Web developers need to know.
We will not discuss the speed provided by the Internet provider, DNS settings or the geographical location of the resource, but focus on the HTTP protocol and those methods that we can use to accelerate the transfer of CSS and JavaScript content.
So, the main factors affecting the page loading speed are:
- The size of the transmitted content
- The number of HTTP requests
Accordingly, our goal is to reduce the size of the transmitted content and the number of requests to the server to a minimum.
Problem 1: Content Transferred
Consider what happens when the page loads. Modern Internet resources with a rich user interface send dozens and sometimes hundreds of HTTP requests to download content. Many of them are in CSS and JavaScript. The total weight of the transmitted CSS and JavaScript is usually several hundred kilobytes or more. You can reduce the amount of transmitted content by minimizing and compressing it.
Minimization is a process whose task is to reduce the script while maintaining its functionality. This is achieved by removing comments from the script, spaces, as well as reducing the names of variables.
The library fully supports YUI Compressorto minimize CSS / JavaScript and gzip for data compression, which can reduce the total weight of the transmitted CSS and JavaScript at times. By default, the library does not minimize resources whose names contain the suffix ".min.".
prop.YUI.OmitFilesFromMinificationRegEx = .*\\.min\\.(js|css)$
You can change the regular expression in
combinatorius.properties
. Since data compression is a resource-intensive process, the final data is cached both on the server and client side, and is provided directly from the cache on subsequent requests. If CSS and JavaScript change, data is minimized, compressed and cached again.
Problem 2: Number of HTTP requests
We need the client to send only two requests, regardless of the number of CSS and JavaScript resources on the page. One per CSS and JavaScript respectively. Let's see what the benefits of this approach are.
As RFC 2616 states, an HTTP request must conform to the format:
Request = Request-Line
*(( general-header
| request-header
| entity-header ) CRLF)
CRLF
[ message-body ]
In real life, it will look something like this:
GET /Protocols/rfc2616/rfc2616-sec5.html HTTP/1.1
Host: www.w3.org
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36
HTTPS: 1
Referer: https://www.google.ie/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Cookie: _ga=GA1.2.587820689.1448903370; JSESSIONID=00002Fn37WPDiDzeIspqmDaEY1J:-1; web_vid=1140991966240108
Based on the same RFC 2616, the HTTP response format is as follows:
Response = Status-Line
*(( general-header
| response-header
| entity-header ) CRLF)
CRLF
[ message-body ]
Chrome DevTools will show us something like:
HTTP/1.1 200 OK
Date: Tue, 12 Apr 2016 15:56:01 GMT
Last-Modified: Thu, 18 Feb 2016 10:16:05 GMT
ETag: "19982-52c08a77e8340"
Accept-Ranges: bytes
Content-Length: 104834
Keep-Alive: timeout=10, max=100
Connection: Keep-Alive
Content-Type: text/css
X-Pad: avoid browser bug
The size of HTTP headers can vary from 200 bytes to 2KB or more, taking into account Cookies.
Funny, but most of all you have to pay for the little things. For example, creating a page with ten static resources, each one byte in size, the browser will have to send and receive in response a few kilobytes of HTTP headers alone to download 10 bytes of useful data.
But the main problem is not even that, but that - the requests are slow. Modern browsers are multi-threaded and try their best, but somehow for almost every HTTP request you need to determine the DNS, create a connection to the server, then SSL handshake, in the case of HTTPS ... And only after that we can get an HTTP response from the server. All this takes time, and the more requests, the more time it takes to load the page.
Fortunately, HTTP headers can be extremely useful, and the library skillfully arranges them to maximize the speed of loading CSS and JavaScript.
Cache-Control (HTTP / 1.1)
The Cache-Control header directives determine who can cache the HTTP response, under what conditions and for how long. It’s best not to send the request at all, but to save a copy of the response in the browser’s cache and take it from there so as not to communicate with the server. This eliminates the need to pay for data transfer over the network.
So the max-age directive determines the maximum time in seconds during which the received response can be reused from the browser cache. The library caches data for one year by default.
Cache-Control: public, s-maxage=31536000, max-age=31536000
You can change the configuration at
combinatorius.properties
.Expires (HTTP / 1.0)
This header is essentially an analogue of Cache-Control, which has supplanted it in HTTP / 1.1. Expires also determines how long data can be cached on the client side. The library sets Expires one year ahead by default.
Expires: Thu, 15 Apr 2017 22:00:00 GMT
You can change the configuration at
combinatorius.properties
.ETag (HTTP / 1.1)
Provides cache validation and allows the client to send a conditional request. This allows the cache to be more efficient, since the web server does not need to send a full response if the content has not changed. The library uses ETag similarly to using fingerprints. For example, there is no need to change the names of CSS and JavaScript resources, after making changes to them, with long caching. The library automatically recognizes changes made to CSS and JavaScript. Data is automatically minimized if necessary, compressed, cached and delivered to the client with all the necessary headers.
User guide
The library is accessible from a central repository.
com.dkiriusin combinatorius 1.0.60 com.yahoo.platform.yui yuicompressor 2.4.8
Register servlet c
web.xml
.Combinatorius com.dkiriusin.combinatorius.CombinatoriusServlet 0 Combinatorius /combo/*
, from now on, all requests to
/combo/*
will be processed by the library. All that is needed next is to create a file
combinatorius.properties
and place it in the Classpath. Using Tomcat as an example, you can achieve this by changing
common.loader
to catalina.properties
and adding the path to combinatorius.properties
. In my case (Ubuntu 12.04 LTS):view /etc/tomcat7/catalina.properties
Before:
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar,/var/lib/tomcat7/common/classes,/var/lib/tomcat7/common/*.jar
After:
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar,/var/lib/tomcat7/common/classes,/var/lib/tomcat7/common/*.jar,${catalina.base}/combinatorius-conf
Accordingly, create a directory:
mkdir /var/lib/tomcat7/combinatorius-conf
And copy to it
combinatorius.properties.
combinatorius.properties
#---------------------#
# required properties #
#---------------------#
# root CSS directory
prop.css.dir = /var/lib/tomcat7/webapps/my_project/css
# cached CSS directory
prop.css.cache.dir = /var/lib/tomcat7/webapps/my_project/css_cache
# root JS directory
prop.js.dir = /var/lib/tomcat7/webapps/my_project/js
# cached JS directory
prop.js.cache.dir = /var/lib/tomcat7/webapps/my_project/js_cache
#---------------------#
# optional properties #
#---------------------#
# themes root directory
prop.themes.dir = /var/lib/tomcat7/webapps/my_project/themes
# Cache-Control: s-maxage directive (31536000 by default)
prop.s-maxage = 31536000
# Cache-Control: max-age directive (31536000 by default)
prop.max-age = 31536000
# Enables gzip compression (true by default)
prop.isCompressionEnabled = true
# Enables YUI compressor (true by default)
prop.isYUICompressorEnabled = true
# Insert line breaks in output after the specified column number (-1 by default)
prop.YUI.CSSCompressor.linebreakpos = -1
# Splits long lines after a specific column (100 by default)
prop.YUI.JavaScriptCompressor.linebreak = 100
# Minify only, do not obfuscate (false by default)
prop.YUI.JavaScriptCompressor.nomunge = false
# verbose output (false by default)
prop.YUI.JavaScriptCompressor.verbose = false
# Preserve unnecessary semicolons (such as right before a '}') (false by default)
prop.YUI.JavaScriptCompressor.preserveAllSemiColons = true
# Disable all the built-in micro optimizations (true by default)
prop.YUI.JavaScriptCompressor.disableOptimisations = true
# Define files to be omitted of minification ('.*\.min\.(js|css)$' by default)
prop.YUI.OmitFilesFromMinificationRegEx = .*\.min\.(js|css)$
The library works with CSS and JavaScript resources in
prop.css.dir
and prop.js.dir
directories, as well as their sub-directories. CSS and JavaScript files are recursively read in alphabetical order, minimized, compressed and sent to the client. Minimized data is cached on the server side in the prop.css.cache.dir
and directories prop.js.cache.dir
. Resources matching a regular expression are
prop.YUI.OmitFilesFromMinificationRegEx
not minimized.CSS themes
Support for CSS themes is also provided. CSS theme is a
prop.themes.dir
sub-directory with one or more CSS files. For example prop.themes.dir/green/theme.css
. The topic name must match the name of the sub-directory and can be passed to the library as a parameter URL theme
or as a value combinatorius.theme
in Cookies.Connect additional resources
It is possible to connect additional resources that are not included in
prop.css.dir
and prop.js.dir
. Such a need may arise if the script is rarely used (on one or two pages in the project) and should not be included in the "assembly" by default. You can transfer additional resources using the parameter URL resources
./combinatorius/combo/&type=js&resources=extra_js/extra1.js,extra_js/extra2.js&theme=blue
Jsp tag
For simplicity and reliability, I recommend using the JSP tag to generate URLs. One tag per CSS and JavaScript respectively. Mandatory attributes are
type
and path
.<%@ taglib uri="https://github.com/deniskiriusin/combinatorius" prefix="cb" %>
blue extra_css/extra1.css,extra_css/extra2.css
Using a JSP tag has one important advantage. The tag automatically subscribes resources by adding a version at the end of the URL to solve cache cache busting problems.
/combinatorius/combo/&type=js&v=1465737376000
Read links:
developer.yahoo.com/performance/rules.html
developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/?hl=en