Compressing Website Assets

Preface

There are different techniques you can use to optimize page loading times. Minification and combing assets to name a few. The best results can be achieved when combining several techniques. Further down I’ll show you what I use. As a starting point I have my CSS and JS minified by SASS and respectively - UglifyJS. I have this automated with the help of NodeJS and my IDE of choice.

The Resources

Compression is the front-ends’ best friend when it comes to highly optimized page loads times.

Images

Regarding the images probably the most important factor that influences the size of the resulting image is the format. For now there are mainly tree widespread formats (it reads widespread)

  • PNG
  • JPG
  • SVG
  • others, subject of another article

Styles

CSS is the next candidate suitable for compression. It doesn’t matter if you hand-craft you CSS, generate or pre-process it is very compressible even after being minified.

Scripts

JavaScript is the next and last resource that will cover here. Just like the CSS it will compress nicely no matter "pure", generated or minified.

Fonts

Other very commonly used site assets are the Web-fonts. Whether like it or not one have to use all of the available formats to support the majority of the browsers. As a rule of a thumb you will use one of many available font foundries out there so you have not to worry about the compression. So I won’t cover them here.

Those are the resources one will want to compress to gain faster resource transmitting, resulting in faster page load and hopefully better UX. Now let’s take a look at the compression algorithms.

The Compressions

Probably the most widespread compression algorithm is DEFLATE. The other one of the same rank is GZIP. I can’t think of a browser that does not support at least one of them. There of course are others, more exotic algorithms with limited or no browser support. The recently adopted one is brotli. Although, developed by Google the first adopter in production is Firefox. So to wrap them up

  • DEFLATE - widely used and supported amongst all browsers;
  • GZIP - roughly the same support as DEFLATE;
  • brotli - brand new, only release Firefox 44 and Chrome/Chromium behind a runtime configuration flag.

All tree algorithms can compress all of the aforementioned resources with varying compression ratio and speed of compression/decompression.
Algorithms — covered. Now let’s head to the needed tools.

The Tools

Tools for handling DEFLATE and GZIP exists more than 20 years. Those are the well known and used zip and respectively gzip commands of your terminal.
On the other hand the latest brotli specification is from December last year.

If you have spend enough time finding better ways to optimize images from the one Photoshop offers probably have stumbled on zopfli - another Google developed compression algorithm. A quick look at the documentation reveals that it can produce valid DEFLATE and GZIP streams with better compression. What’s the catch? It is slow. This should not bother you since compressing assets naturally is done once on deployment.

After very quick download, compilation and install both bro and zopfli are ready for some crunching. The documentation is clear and easy to follow.
For now we have gzip, zip or zopfli and bro.

Compressing assets

A nicely done tool for automating compression can be found on-line. I use heavily modified version of the script provided at the bottom of the page to make use of the new compression algorithms and adding more resources to compress:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/usr/bin/perl

use warnings;
use strict;

use File::Find;

if ($ARGV[0] eq "") {
$ARGV[0] = ".";
}

find(\&wanted, ($ARGV[0]));
sub wanted
{

if (/(.+\.(?:html|css|js|json|svg|png|jpg|gif|ico)$)/) {
my $br = "$_.br";
my $df = "$_.df";
my $gz = "$_.gz";

if (-f $gz) {
if (age ($_) <= age($gz)) {

return;
}
}

# The following substitution is for the case that the file
# name contains double quotation marks ".
$_ =~ s/"/\\"/g;

# Now compress the file.
# system("gzip --keep --best --force \"$_\"");

print($_ . " \r");

# Now compress the file even better.
system("bro --force --input \"$_\" --output \"$br\" --quality 99");
system("zopfli --i1000 -c --deflate \"$_\" > \"$df\"");
system("zopfli --i1000 -c --gzip \"$_\" > \"$gz\"");
}
}

# This returns the age of the file.

sub age
{

my ($file) = @_;
my @stat = stat $file;

return $stat[9];
}

Note: I use quite high compression options and as turns out very time consuming. Presuming that you want to achieve heig compression and the fact that files are compressed once per change justifies the time-consuming operation.

Alright, assets compressed, but will the server handle them?

The Server

Nginx and Apache. You know them well. I use apache for staging/prod and nginx for dev environment. To be frank, I setup only the apache. There are various articles on the Net providing enough info how to setup nginx.
The apache configuration file I use can be found below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
<IfModule mod_rewrite.c>

RewriteEngine on
RewriteBase /

# BROTLI
AddEncoding br .br

# If brotli encoding is supported by the browser
# and corresponding version of the file exists
# point the browser to it
RewriteCond %{HTTP:Accept-Encoding} br
RewriteCond %{REQUEST_FILENAME}.br -f
RewriteRule ^(.*)$ $1.br [L]

<FilesMatch \.html\.br$>
ForceType text/html
</FilesMatch>
<FilesMatch \.css\.br$>
ForceType text/css
</FilesMatch>
<FilesMatch \.js\.br$>
ForceType application/javascript
</FilesMatch>
<FilesMatch \.txt\.br$>
ForceType text/plain
</FilesMatch>
<FilesMatch \.json\.br$>
ForceType application/json
</FilesMatch>

<FilesMatch \.svg\.br$>
ForceType image/svg+xml
</FilesMatch>
<FilesMatch \.png\.br$>
ForceType image/png
</FilesMatch>
<FilesMatch \.jpg\.br$>
ForceType image/jpg
</FilesMatch>
<FilesMatch \.gif\.br$>
ForceType image/gif
</FilesMatch>
<FilesMatch \.ico\.br$>
ForceType image/x-icon
</FilesMatch>


# DEFLATE
AddEncoding deflate .df

# If deflate encoding is supported by the browser
# and corresponding version of the file exists
# point the browser to it
RewriteCond %{HTTP:Accept-Encoding} deflate
RewriteCond %{REQUEST_FILENAME}.df -f
RewriteRule ^(.*)$ $1.df [L]

<FilesMatch \.html\.df$>
ForceType text/html
</FilesMatch>
<FilesMatch \.css\.df$>
ForceType text/css
</FilesMatch>
<FilesMatch \.js\.df$>
ForceType application/javascript
</FilesMatch>
<FilesMatch \.txt\.df$>
ForceType text/plain
</FilesMatch>
<FilesMatch \.json\.df$>
ForceType application/json
</FilesMatch>

<FilesMatch \.svg\.df$>
ForceType image/svg+xml
</FilesMatch>
<FilesMatch \.png\.df$>
ForceType image/png
</FilesMatch>
<FilesMatch \.jpg\.df$>
ForceType image/jpg
</FilesMatch>
<FilesMatch \.gif\.df$>
ForceType image/gif
</FilesMatch>
<FilesMatch \.ico\.df$>
ForceType image/x-icon
</FilesMatch>


# GZIP
AddEncoding gzip .gz

# If gzip encoding is supported by the browser
# and corresponding version of the file exists
# point the browser to it
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{REQUEST_FILENAME}.gz -f
RewriteRule ^(.*)$ $1.gz [L]

<FilesMatch \.html\.gz$>
ForceType text/html
</FilesMatch>
<FilesMatch \.css\.gz$>
ForceType text/css
</FilesMatch>
<FilesMatch \.js\.gz$>
ForceType application/javascript
</FilesMatch>
<FilesMatch \.txt\.gz$>
ForceType text/plain
</FilesMatch>
<FilesMatch \.json\.gz$>
ForceType application/json
</FilesMatch>

<FilesMatch \.svg\.gz$>
ForceType image/svg+xml
</FilesMatch>
<FilesMatch \.png\.gz$>
ForceType image/png
</FilesMatch>
<FilesMatch \.jpg\.gz$>
ForceType image/jpg
</FilesMatch>
<FilesMatch \.gif\.gz$>
ForceType image/gif
</FilesMatch>
<FilesMatch \.ico\.gz$>
ForceType image/x-icon
</FilesMatch>
</IfModule>

<IfModule mod_headers.c>
<FilesMatch ".(html|css|js|json|svg|png|jpg)$">
Header append Vary: Accept-Encoding
</FilesMatch>
</IfModule>

This configuration ensures the highest compression is being served to your users depending on their browser support. It is not based on user agent sniffing rather feature detection.

Epilogue

To some compressing minified files or even images may seem redundant or unneeded but saving even the smallest amounts of traffic to the user can be perceivable not only to them but for your wallet too when serving large amounts of data.