Nanoc3 with Rack::StaticCache
There is a neat piece of middleware introduced in rack-contrib 0.9.3 called Rack::StaticCache. It allows you to version your static assets (images, css) so that you can set infinite expires headers on them. All you need is a version number trailing your file name, and it is routed through to the underlying file. Whenever you change the file, you change the version.
1 2 |
/img/lolcat-1.jpg -> /img/lolcat.jpg /img/lolcat-2.jpg -> /img/lolcat.jpg |
The URLs go to the same place, but since they are different you can cache them indefinitely and change all the referencing URLs in your code when you change the asset. That’s annoying if you’re trying to do it by hand, but that’s why we have code eh. I wrote a nanoc3 after filter that parses the HTML using nokogiri, and replaces any reference to any image or stylesheet with a reference versioned using the last modified timestamp of that asset. It automatically updates! This is particularly neat because you can link in images in markdown without ever worrying about versioning.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# lib/static_cache_filter.rb require 'nokogiri' class StaticCacheFilter < Nanoc3::Filter identifier :static_cache def run(content, params = {}) doc = Nokogiri::HTML::Document.parse(content) add_version = lambda {|attr| lambda {|x| src = x[attr] item = @items.detect {|y| y.identifier == "#{src.gsub(/\..+$/, '')}/" } if item version = item.mtime.to_i tokens = src.split('.') src = tokens[0] + "-#{version}." + tokens[1..-1].join('.') x[attr] = src end }} doc.css('img' ).each(&add_version['src']) doc.css('link[rel=stylesheet]').each(&add_version['href']) doc.to_html end end |
1 2 3 4 5 6 |
# Rules compile '/' do filter :haml layout 'home' filter :static_cache end |
1 2 3 |
# config.ru use Rack::StaticCache, :urls => ['/img','/css'], :root => "public" run Rack::Directory.new("public") |
Nanoc3 and CoffeeScript
Nanoc3 is a pretty awesome static site generator. It works by running your content through “filters” to create the final static site. It comes with a lot of built in filters – Haml, Sass, rubypants, markdown, and more! Nothing for Javascript though. Which is sad because I really like CoffeeScript. It’s ok! I wrote my own filter, shared here for your enjoyment.
Bang this in your lib folder:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
require 'open3' require 'win32/open3' if RUBY_PLATFORM.match /win32/ class CoffeeFilter < Nanoc3::Filter identifier :coffee def run(content, params = {}) output = '' error = '' command = 'coffee -s -p -l' Open3.popen3(command) do |stdin, stdout, stderr| stdin.puts content stdin.close output = stdout.read.strip error = stderr.read.strip [stdout, stderr].each { |io| io.close } end if error.length > 0 raise("Compilation error:\n#{error}") else output end end end |
To use it, a compilation rule like the following is pretty neat:
1 2 3 4 5 6 7 8 9 |
# Compile both coffee and js, co-mingled in the same directory compile '/js/*' do case item[:extension] when 'coffee' filter :coffee when 'js' # Nothing end end |
Don’t forget to add ‘coffee’ to the list of text extensions in your config.yaml!
Protip: You can use the above pattern to filter content through any command line program. Figlet anyone?