Minifying Jekyll Content on a Shared Host

I love Jekyll. It makes it super simple for me to create the content for this site. All I have to do is open up a text file and start typing. When I’m done, it’s just a simple git push away from the web where it’s formatted and compiled into static files.

The biggest reason why I love Jekyll is the pure minimalism factor. I can’t stand bloat. Loading up a page and seeing the network requests in the hundreds and the page size in the megabytes sends shivers down my spines. When I’m coding, I write things as efficiently and legibly as possible whenever I can, and always spend time refactoring my code until I’m pleased.

So when it comes to my place on the web, I’m not going to make an exception. Now, when it comes to web content, I’m left with an interesting dillema. I can do one of two things:

  1. Make the code very legible with whitespace, or
  2. Make the code very small by ‘minifying’ it.

Well, after a lot of thinking, I decided to go with the latter. My thinking is that any half-decent web browser will already pretty-print the code when viewing the source, and barring that, someone could easily throw it into one of many beautifiers available anyway. In this case, the bytes are more important.

The next step was to figure out how to minify the code automatically. Initially, I looked a plugin: a simple Ruby file that’s dragged into the plugin directory and changes the way Jekyll behaves. As it turns out, Stereobooster has already made jekyll-press to do just that. Unfortunately, it has some dependencies that I just couldn’t install on a shared host.

So like any other logical programmer, instead of settling for less, I wrote my own.

The dependencies of my script can all be installed in the userspace without the need of gcc or other compilers. To get started (assuming you have Python Pip), simply run the following code:

pip install --user htmlmin
pip install --user slimit
pip install --user csscompressor

This will install them all to the home directory.

Next, create a file named _tidy.py with the following content:

# -*- coding: utf-8 -*-
"""
_tidy: Reformats and minifies HTML, JS, and CSS
_tidy [directory_in] [directory_out]

Requires:
- slimmer
- slimit
- csscompressor
To install on shared hosting, use `pip install --user <package>`

"""
#from htmlmin import minify as html_minify
from slimmer import css_slimmer as css_minify
from slimmer import html_slimmer as html_minify
from slimit import minify as js_minify
#from csscompressor import compress as css_minify
import os
import sys

reload(sys) # Python deletes setdefaultencoding, so get it back
sys.setdefaultencoding('utf8') # Otherwise, htmlmin will use ascii encoding

input_dir = u'./_site' if len(sys.argv)<=1 else sys.argv[1]
output_dir = u'./_minify' if len(sys.argv)<=2 else sys.argv[2]
valid_extensions = ['.html', '.js', '.css']
file_counts = [0]*len(valid_extensions)
compression = {
    '.html' : lambda content: html_minify(content),
    '.js'   : lambda content: js_minify(content, mangle=True),
    '.css'  : lambda content: css_minify(content)
}

for root, dirs, files in os.walk(input_dir):
    for file in files:
        file_in  = os.path.join(root, file)
        file_out = os.path.join(output_dir, os.path.relpath(file_in, start=input_dir))
        extension = os.path.splitext(file)[-1]

        if extension in valid_extensions:
            with open(file_in, 'r') as f_in:
                contents = f_in.read()

            try:
                os.makedirs(os.path.dirname(file_out))
            except Exception: # If the directory already exists
                 pass
            
            try:
                minified = compression[extension](contents)
            except Exception, e:
                print "Error parsing '%s' file '%s'" % (extension[1:], file)
                print ">>>%s" % e
            else:
                with open(file_out, 'w') as f_out:
                    file_counts[valid_extensions.index(extension)] += 1
                    f_out.write(minified)


print "Compressed %s file(s)" % sum(file_counts)
for extension, count in enumerate(file_counts):
    print "-> %s: %s" % (valid_extensions[extension], count)

Run it with python _tidy.py in_dir out_dir, or in my case, python _tidy.py $JEKYLL_TMP/build.raw $/JEKYLL_TMP/build.min

Good luck!!