Extending Jekyll
After using Jekyll to build my personal website, I was having some issues with my stylesheet not being reloaded by Firefox. Frameworks like Ruby on Rails provide an HTML helper that add a short querystring which is simply the last modification time of the file being linked to. This interacts nicely with the browse’s cache since it can cache the stylesheet and when you change it, the URL will be different (with the new mtime) and the browser will load the new stylesheet.
Rails provides stylesheet_link_tag
, which you use as follows:
<%= stylesheet_link_tag 'app', :media => "all" %>
stylesheet_link_tag
then renders the following HTML:
<link href="/stylesheets/app.css?1234672637" media="all" rel="stylesheet" type="text/css" />
Where 1234672637I
is the last modification time (or mtime) of the stylesheet file. I wanted to have the same feature in Jekyll. Jekyll uses of the Liquid text templating system, and Liquid supports being extended allowing you to add your own filters, tags and tag-blocks. Tags are used for inserting content:
<content type="html">{ { post.content } }</content>
Filters are used to transform content, an example from the atom.xml formats the date:
<updated>{ { post.date | date_to_xmlschema } }</updated>
What I wanted to be able to write was something like:
{ % stylesheet "/css/style.css" % }
Which meant I had to make a new tag. I created the tag file lib/jekyll/tags/stylesheet_link.rb
:
module Jekyll class StylesheetTag < Liquid::Tag def initialize(tag_name, file, tokens) super @file = file end def find_stylesheet(context) file = @file file.strip! file.gsub! /^["']/, "" file.gsub! /["']$/, "" if ! file =~ /.[a-z]+$/ file = "#{file}.css" end files = [File.join(context.registers[:site].source, @file), # strip a leading slash File.join(context.registers[:site].source, @file[1..-1]), ] files.each {|file| if File.exists? file return file end } return file end def render(context) file = find_stylesheet(context) mtime = nil if ! File.exists?(file) warn "Stylesheet file: '#{@file}' not found (#{file})" mtime = rand 1000000000 else mtime = File.mtime(file).to_i end return %Q{<link rel="stylesheet" href="#{@file}?#{mtime}" type="text/css" media="screen, projection" />} end end end Liquid::Template.register_tag('stylesheet', Jekyll::StylesheetTag)
All tags derive from Liquid::Tag and implement both initialize
and render
. In the constructor the StylesheetTag
saves off the argument (file). In render
it attempts to find the css file and then either uses a random value or the mtime of the file (if it was found), finally returning the string representing the stylesheet link.
Finally I added the module to the list of requires in lib/jekyll.rb
so it is loaded when Jekyll runs:
... # internal requires require 'jekyll/core_ext' require 'jekyll/pager' require 'jekyll/site' require 'jekyll/convertible' require 'jekyll/layout' require 'jekyll/page' require 'jekyll/post' require 'jekyll/filters' require 'jekyll/tags/highlight' require 'jekyll/tags/include' require 'jekyll/tags/stylesheet_link' require 'jekyll/albino' ...
Once that was completed the stylesheet tag generated links with the mtime as a query parameter and my stylesheets no longer had the caching issue. I’m offering my changes back to Tom Preston-Werner, but you can fork or clone my repository if you want to try them yourself.