Minifying Webfont CSS

This morning I wrote a small script to both unify and minify CSS files that define web fonts.

Drawing of Hesperocyon
Artist rendition of what Hesperocyon may have looked like.

Don’t mind the picture, it is an artist rendition of what we think the common ancestor of all current members of the canine family looked like, and it is here so that twitter does not just post a blank image when I share this page there. And because it is cool.

Minifying JS and CSS

When serving JS and CSS, it is a good idea to minify them. They often are needed for the page to be fully functional and minifying them reduces the bandwidth needed to serve them. Minifying makes a big difference to users who have extremely limited bandwidth.

With respect to minifying JavaScript and CSS, I personally normally do it on the fly with a php class I wrote (it makes use of a php class someone else wrote)

What I have is a php wrapper script that the .htaccess file calls. If the JS or CSS filename was requested with a unix timestamp at the end of the filename, it gets minified on the fly by php and served with instructions to cache for a very long time. The wrapper script I wrote also takes care of browser requests to see if the cached version it has is current.

When adding the JS or CSS file to the webpage, the UNIX timestamp of the original is added. That way, any changes I make to a JS or CSS file result in a new timestamp so I do not have to worry about stale versions being served to the user, and the browser can cache it a really long time.

Webfont CSS

When your web application uses web fonts, they are defined in a CSS file. With the exception of this WordPress blog, I prefer to serve webfonts myself so that they are not a source of tracking for my users.

The JS/CSS minification my web applications do does not work with my webfonts though because webfonts are served from a cookieless domain I run different from the server that serves the web applications, so the web application can not check the timestamp on the CSS file to see if it changed. Well it could if I created a way for the web application to ask the font server, but that introduces latency and other issues.

Also, my php class that minifies CSS removes all comments and I can not do that with webfont CSS files because many of the fonts are commercial fonts and need the license information intact.

Before this morning, I had four different webfont CSS files I would server:

  • ywft-webfonts.css (webfonts licensed from youworkforthem.com)
  • floss-webfonts.css (Free Libre Open Source Software)
  • base35-webfonts (Adobe Postscript Level II fonts I licensed eons ago as Type1 fonts for LaTeX – converted to both woff and woff2)
  • lmono-webfonts.css (Lucida Mono family I licensed eons ago from myfonts.com for LaTeX – converted to both woff and woff2)

Four different CSS files, not being minified. The first two could change as I added fonts, the last two never change.

Anyway, SEO analyzers always complained about the number of CSS files and that they were not minified.

With respect to the number, that’s bullshit. Most browsers these days use HTTP/2 and my server supports HTTP/2 meaning a single connection can be used to request all four files (along with jQuery served from the same server).

But they had a point about the lack of minification, and while I could not remove the license related stuff, I did have comments to myself within the CSS files that really did not need to be served to every fucking browser that requests it.

So – early this morning I finally scripted a solution to both issues, using the yuicompressor utility (a command line Java JS/CSS minifier, available for CentOS 7 from the EPEL package repository – yum install yuicompressor will fetch it for you)

When you start a CSS comment block, you start it with /* and end it with */

To tell yuicompressor not to remove it, start it with /*! instead.

The Script

Here is the script, broken into pieces. It is small but I want to explain each piece.

#!/bin/bash
pushd /srv/teasenetwork.com/webfonts > /dev/null 2>&1

I will be running it from cron every fifteen minutes, so we want output of the pushd command redirected to /dev/null or the cron daemon will be e-mailing me the output every fifteen minutes.

rm -f tmp.css && touch tmp.css
rm -f tmp2.css
rm -f tmp3.css
rm -f tmp4.css

Not completely necessary, but just make sure those four files are wiped clean. I could do it in less that four temporary files, but… I like to keep things simple.

for css in ywft-webfonts.css floss-webfonts.css base35-webfonts.css lmono-webfonts.css; do
  cat ${css} >> tmp.css
  echo -e "\n\n" >> tmp.css
done

Puts the contents of all four files into a single file. The echo is not really necessary, I just wanted new lines between them so I could look at tmp.css and see where one file ended and other began.

yuicompressor -o tmp2.css tmp.css

That takes the unified CSS file and minifies it, but preserving the comments that are marked to not minify.

cat tmp2.css \
|sed -e s?"\/\*"?"\n/*"?g \
|sed -e s?"\/\*\!"?"/*"?g \
|sed -e s?"\*\/"?"*/\n"?g > tmp3.css

yuicompressor compressor does not start a preserved comment blocks on a new line, I want them to start on a new line, that is what the first sed does.

The second sed removed the ! from the beginning of the comment block that told yuicompressor to leave that comment intact.

yuicompressor compressor does not insert a new line after a preserved comment block, the third sed adds that.

sed '/^$/d' tmp3.css > tmp4.css

The result of the sed commands that fixed how I like comments was some empty lines that I didn’t want, one at top of the file and anywhere one preserved comment block directly followed the other. I could have added that to the first chain of sed commands but I didn’t.

cmp --silent webfonts.css tmp4.css || cat tmp4.css > webfonts.css

If the result differs from the existing webfonts.css file and only if the result differs, the existing webfonts.css file is replaced by the result.

That way when browsers check to see if their cached copy is still good, even though the script runs from cron, the inode and timestamp will only be different if the content of one of the four starting files actually changed.

popd > /dev/null 2>&1
exit 0

We’re done.

There is a .htaccess file that lets me request the file by a different name:

RewriteEngine on
RewriteBase /webfonts/
RewriteRule ^webfonts-[0-9]+\.css$ webfonts.css
AddType font/woff2 .woff2
AddOutputFilterByType DEFLATE text/css
<FilesMatch "\.(woff|woff2)$">
Header set Cache-Control "max-age=7257600"
Header set Access-Control-Allow-Origin "*"
</FilesMatch>

<FilesMatch "\.css$">
Header set Cache-Control "max-age=7257600"
ForceType 'text/css; charset=UTF-8'
</FilesMatch>

The web applications just append the YYYYMMDD that corresponds with the first day of the week to the filename, so once a week the browser will always fetch a new copy, and if I add a font and need the web application to immediately fetch a new copy, I can tell the web application to add 1 to the YYYYMMDD so anyone who already cached it that week gets a fresh copy too.

Leave a comment