From the translator: This is the fourth article from the Node.js series from the Mozilla Identity team that deals with the Persona project. This article is about how to increase the speed of loading site resources and the connect-cachify module created in Mozilla for this purpose.
All articles of the cycle:
In this article, we’ll talk about front-end performance and present the tools that Mozilla created to make the Persona site as fast as possible. We describe the work with connect-cachify, a module that automates some of the most important aspects that ensure front-end performance.
')
But first, let's take a look at the most common ways to improve performance. If you already have experience in optimizing the frontend, you can skip these sections and go to the end of the article, which describes how to use connect-cachify to automatically do what you used to do manually.
Three 'K' client performance
The network is full of information on how to achieve high performance. There are dozens of tricks that can save a millisecond or two here or there, but three techniques — concatenation, compression, and caching — always form the basis for optimization.
Concatenation
The purpose of concatenation is to minimize the number of requests to the server. Requests are expensive. The time to open an HTTP connection is often longer than the time it takes to transfer data. Every extra request slows down the site, especially on mobile devices, where network delays can be very large. Have you ever had to go to any online store from a mobile phone on EDGE and painfully wait for the alternate loading of multiple images?
The new protocol
SPDY, which has already become the basis for the specification of the new version of the HTTP 2.0 protocol, is able to combine requests to several resources into one HTTP connection. Unfortunately, so far his support is only in the latest versions of Chrome, Firefox and Opera.
Combining several resources into one in manual mode, in the old-fashioned way, works anywhere and will work even after the widespread distribution of SPDY. There are many tools for combining the main types of resources - JavaScrit, CSS and images.
Javascript and css
If the site uses more than one external JavaScript file, then you should think about merging all these files into one. Browsers suspend page rendering while scripts are loading. Since each request requires time overhead, the example below works slower than it could:
<script src="jquery.min.js"></script> <script src="main.js"></script> <script src="image-carousel.js"></script> <script src="widget.js"></script>
By combining several requests into one, you can significantly reduce load times:
<script src="main.production.js"></script>
Working with the combined file while writing code and debugging is very inconvenient, therefore, usually concatenation is done only for the finished site. CSS files are merged in the same way as JavaScript.
Images
The two main techniques for reducing the number of requests for loading images are embedding data in URLs and sprites.
data: URI
data: URI is a special form of URL that allows you to embed small images as a URL directly into the text of an HTML or CSS document. The data URI can be used both in the
src
attribute of the
img,
tag and in the url of the background image in CSS. Since embedded images are encoded in base64, they take up more space, but require one request less than normal images. If the picture is small enough, the gain due to the decrease in the number of requests is greater than the loss in size. IE6 and IE7 do not support data: URI, keep this in mind.
Sprites
Sprites are a great alternative to data: URI. Sprite is a collection of images combined into one big picture. Using CSS you can cut out small images from it. The lack of sprites - inconvenience in maintenance and modification. Adding or modifying images in the sprite may require corresponding changes in the CSS.
There are many tools for creating sprites from a set of individual images. One of them is the
Sprite Cow site, which generates CSS code for sprites based on the loaded image.
Remove extra bytes - minification, optimization and compression
Consolidating several resources into one to minimize the number of HTTP requests is a big step towards speeding up the site, but more can be achieved. After concatenation, you need to reduce the total number of bytes sent over the network. This is usually done in the form of minification, optimization and compression.
Javascript and css
JavaScript and CSS are textual resources that are effectively minified. Minification is the removal of all characters that the browser ignores. Conversion of both JavaScript and CSS begins with the removal of extra spaces, line breaks and comments. In the case of JavaScript, you can go much further. Some minifiers replace multi-character variable names with single-character ones, remove unnecessary language constructs and even replace them with equivalent shorter ones.
Among the popular tools for JavaScript minification are
UglifyJS ,
YUICompressor and
Google Colsure Compiler . For CSS, YUICompressor and
UglifyCSS are often used.
Images
Images often contain redundant data that can be removed without degrading visual quality. This is not difficult, but requires the use of special tools. Francois Marie from our team wrote in more detail on this topic in articles devoted to
working with PNG and
GIF .
Yahoo! offers the online tool
Smush.it .
ImageOptim is a similar offline program for OSX. It can work in fully automatic mode - just drag the necessary files onto it, and they can lose weight noticeably.
If some deterioration in visual quality is permissible, you can additionally squeeze the image with a greater degree of compression.
The server can also help
Even after concatenation and minification there is something to save. Almost all servers and browsers support HTTP traffic compression. The two most popular compression schemes are deflate and gzip. They both use very efficient compression algorithms to reduce the size of the data before they leave the server.
Caching
Concatenation and compression help those who first visited our site. The third "K" - caching, helps regular visitors. A user who has already been on the site should not reload all resources. HTTP offers two mechanisms to achieve this - caching headers and ETags, or entity tags.
There are two kinds of HTTP headers related to caching static resources that never or almost never change. These are the two HTTP response header fields —
Expires
and
Cache-Control: max-age
. The
Expires
field stores the date after which the resource will need to be updated.
max-age
- resource lifetime in seconds. If these fields in the header are set, the browser will request an existing resource only after the specified time has elapsed.
The ETag is the resource version label, which serves to ensure that the local version of the resource matches the actual version on the server. Etag is suitable for dynamic content that can change at any time. When an ETag is installed on a resource, it tells the browser to check the version, and if it has not changed, the resource can be not downloaded. Since the ETag resource still requires a small amount of information to be transferred from the server each time, it is not as effective as the resource with the lifetime indication.
Forced cache cleaning
The advantage of using
Expires
and
max-age
fields over Etag is that the browser requests resources only if they are expired. This is also the main drawback. What if the resource suddenly changes? Sometimes it is necessary to forcibly clear the cache.
This is usually done by adding the version number of the resource to the URL. Any change to the URL causes the new version of the resource to be loaded.
For example, if the lifetime
example.com/logo.png
example.com/logo.png
is one year, but the logo is changing, everyone who has already downloaded the old version will see the new one only in a year. You can deal with this by adding a version identifier to the URL:
http://example.com/v8125/logo.png
or
http://example.com/logo.png?v8125
When the logo changes, the URL will also change, which means all users will download the new version.
Connect-cachify: the Node.js library for concatenating and caching resources
Connect-cachify is a middleware for Node.js, developed by Mozilla to facilitate the concatenation and caching of resources.
In production mode, connect-cachify gives combined and minified resources and sets their lifetime to one year by default. In development mode, instead, resources are given one by one, which makes debugging easier. Connect-cachify does not deal with concatenation and minification, leaving it on the conscience of the build script.
connect-cachify is configured by calling the
setup()
function. It takes two arguments,
assets
and
options
.
assets
is a dictionary that establishes the correspondence between resources in production and development modes. Each combined and minified resource corresponds to several separate development mode.
options
are optional parameters, among which are:
prefix
- the string that gives the hashes in the links (default is empty);production
- a flag that toggles the modes of production and development (by default, true
);root
- full path to the directory with static resources (by default - '.'
).
Connect-cachify usage example
Suppose we have simple HTML that references three CSS files and three separate scripts:
... <head> <title>Dashboard: Hamsters of North America</title> <link rel="stylesheet" type="text/css" href="/css/reset.css" /> <link rel="stylesheet" type="text/css" href="/css/common.css" /> <link rel="stylesheet" type="text/css" href="/css/dashboard.css" /> </head> <body> ... <script type="text/javascript" src="/js/lib/jquery.js"></script> <script type="text/javascript" src="/js/magick.js"></script> <script type="text/javascript" src="/js/laughter.js"></script> </body> ...
We connect connect-cachify to our server, set the correspondence between production and development-resources and configure the module:
In order not to violate the DRY principle, the resource map can be stored in a separate file used for both connect-cachify and the build script.
Then we will update our templates, indicating the place of inclusion of CSS and JavaScript. CSS is connected using the
cachify_css
helper, JavaScript, respectively,
cachify_js
.
... <head> <title>Dashboard: Hamsters of North America</title> <%- cachify_css('/css/dashboard.min.css') %> </head> <body> ... <%- cachify_js('/js/main.min.js') %> </body> ...
Connect-cachify output
If the production flag is set to
false
, connect-cachify will generate three CSS links and three JS links, exactly as in the original HTML above. But if it is set to
true
, only one tag will be created. The URL in each tag will include the MD5 hash of the resource. This is done to force a refresh in the event of a resource change. That's all.
... <head> <title>Dashboard: Hamsters of North America</title> <link rel="stylesheet" type="text/css" href="/v/2abdd020a6/css/dashboard.min.css" /> </head> <body> ... <script type="text/javascript" src="/v/acdd2ab372/js/main.min.js"></script> </body> ...
Conclusion
Many things that can be done to speed up a site are very simple. Even the most basic things, our three "K" - concatenation, compression and caching - can significantly reduce page load time and improve the user experience. Connect-cachify helps automate the concatenation and caching of applications, but there are many other improvements ahead. In the next article on accelerating the frontend, we will explain how to cache dynamic content using ETag.
All articles of the cycle: