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.