Asset bundling with Jekyll
Asset bundling has a related problem: caching. Generally, when the assets of your site change, you want the browser to fetch the latest version from the backend. The problem is in detecting when to use the version in the browser’s cache and when to refetch the latest version of the asset from the backend. This can be solved by setting the HTTP response headers so that html files are considered to be dynamic resources, refetched when changed. The asset files are regarded as static resources, having long caching period. Whenever the contents of an asset changes, we refer to a new static resource in the html file. The latter is called cache busting. There’s two techniques to it: using a URL query parameter or a fingerprint in file path.1
It is up to you to solve both asset bundling and cache busting with Jekyll. Out of the box, Jekyll just copies each source as is to the generated site directory. This does not help asset bundling. And you handle the references to the assets in html files manually without any cache busting mechanism. Let’s see what we can do about these.
Jekyll with GitHub pages
With GitHub pages, you let the service generate the site from your sources. The tradeoff is that GitHub runs Jekyll with
--safe switch, disabling plugins. This means you have to do with what Jekyll has by default.2
Update (9 March 2013): Another alternative is to concatenate assets with
include tags, as shown here.
Then you have to solve cache busting. Here’s one way to do it. In your content file, add a query parameter to the URL of the asset:
When you generate the site, the
bust parameter will have a timestamp from the moment of site generation.
Update (29 December 2014): Changed the example above to use plain Unix timestamp as the cache bust value.
The timestamp will change upon each site generation, likely updating too often compared to the frequency of changes you have for
screen.css. Assets need timestamps to update only when the contents of the assets change. But at least timestamp generation is automatic, so the tradeoff might be okay. I guarantee you won’t remember to do it manually every time when needed.
Jekyll with jekyll-minibundle
In order to address the compromises discussed above, you have to use Jekyll with plugins. If you look at the Jekyll plugin page and search for “asset”, you will find many plugins written for handling asset bundling.
But for my own preferences, I found most of the existing plugins too complex to use. Neither did like to install a lot of transitive gem dependencies. So, I decided to write my own: jekyll-minibundle. The plugin has no gem dependencies and it works with any minification tool supporting standard unix input and output.
First, you need to choose your minification tool. UglifyJS2 is a fast one. Install the tool of your choice and set the path to its executable in
$JEKYLL_MINIBUNDLE_CMD_JS environment variable. For example:
Then, install jekyll-minibundle with
and place the following line to
_assets/scripts directory in the site project.
In your content file where you want the
<script> tag to appear, place a
minibundle Liquid block:
some_sharing.js as input sources from
_assets/scripts directory. These will be fed to the minifier in the given order. The output will be stored to
_site/assets/site-<md5digest>.js. The plugin will insert the MD5 digest over the contents of the bundle as the fingerprint to the filename:
The plugin supports the same mechanism for stylesheets. However, I still like to use Compass for stylesheets, because it has so many other benefits. Because Compass can handle bundling, the plugin only needs to copy the file and add a fingerprint to the filename.
In order to do this, tell git to ignore
_tmp directory, and configure Compass to place the output to
_tmp/screen.css. Then, add this line to your content file for including the path to the bundle:
The resulting filename fill have the MD5 digest of the file as the fingerprint:
This approach works with RequireJS optimizer, too!
Both the fingerprinting and asset bundling mechanisms work in Jekyll’s auto regeneration mode.
The plugin has one more trick in its sleeves. If you set environment variable
development, the plugin copies asset files as such to the destination directory, and omits fingerprinting. This is useful in development workflow, where you need the filenames and line numbers of the original asset sources.
I have shown how to automate asset bundling and fingerprinting for cache busting with the plugin. In addition, we have gotten rid off all the compromises we had when using vanilla Jekyll: there is no need to store generated bundle files in git, and asset fingerprints change only when the contents of the assets change.
You can read more about the plugin at its project page in GitHub. Also, you might be interested in a site that uses the plugin just like described above.
Google recommends cache busting with fingerprinting over using a query parameter. Some old proxy caches do not cache static files at all if the URL contains query parameters. ↩
However, you can work around this by generating your site locally and then pushing the generated files to GitHub. Then you’re not locked to Jekyll’s safe mode. ↩