Blogging with Jekyll, Haml, Sass, and Jammit
It seems anyone who uses Jekyll to run their blog ends up blogging about the particular way they've set it up. So here's mine.
I came to Jekyll from Wordpress, which I found easy to set up but way too hard to change when something wasn't quite right. The great thing about Jekyll is that it doesn't generate anything on the fly — all HTML is generated at once, meaning your web server only has to serve raw files. Less moving parts, less things that can break.
Haml/Sass for both templates and layouts
First thing I noticed was a lack of Haml and Sass support, which simply wouldn't do. Once you've had a taste of concise HTML/CSS, you never want to go back. While Jekyll doesn't support either out of the gate, a quick Googling revealed a very simple Haml/Sass converter script, which I added in _plugins/haml_converter.rb:
module Jekyll
require 'haml'
class HamlConverter < Converter
safe true
priority :low
def matches(ext)
ext =~ /haml/i
end
def output_ext(ext)
".html"
end
def convert(content)
engine = Haml::Engine.new(content)
engine.render
end
end
require 'sass'
class SassConverter < Converter
safe true
priority :low
def matches(ext)
ext =~ /sass/i
end
def output_ext(ext)
".css"
end
def convert(content)
engine = Sass::Engine.new(content)
engine.render
end
end
end
So far, so good. One problem though, Jekyll doesn't evaluate layouts through markup converters the same way it does with posts, so my layouts will have to be in plain HTML. Or will they? I stuck my haml layouts in _layouts/haml, and added a rake task, also lifted from Google, to Haml-ize any layouts:
desc "Parse haml layouts"
task :parse_haml do
print "Parsing Haml layouts..."
system(%{
cd _layouts/haml &&
for f in *.haml; do [ -e $f ] && haml $f ../${f%.haml}.html; done
})
puts "done."
end
Add a pair of rake tasks to run jekyll one-time or in preview mode:
desc "Launch preview environment"
task :preview do
Rake::Task["parse_haml"].invoke
system "jekyll --auto --server"
end
desc "Build site"
task :build do |task, args|
Rake::Task["parse_haml"].invoke
system "jekyll"
end
Minify assets with Jammit
Jammit is an "industrial strength asset packaging library for Rails" which combines groups of js scripts and css files into single asset files, then runs those assets through minifiers which shrink them and hopefully makes your pages load a bit faster. Jekyll is not Rails, but you can get Jammit working without too much trouble.
First off, you need a YAML config file to configure Jammit. I put mine in _assets.yml with the following contents:
javascript_compressor: closure
javascripts:
application:
- _site/js/jquery-1.5.2.js
- _site/js/jquery.cookie.js
- _site/js/jquery.twitter.js
- _site/js/app.js
stylesheets:
screen:
- _site/css/screen.css
- _site/css/syntax.css
Now when I run jammit -o assets -c _assets.yml, I get nice streamlined javascript and css assets. However, one of the things Jammit does with Rails is add in helper methods for including the groups of assets into your template files. To emulate this, I had to add some special tags to Jekyll.
Jekyll uses Liquid for templating, and adding new tags in Liquid is easy. _plugins/asset_tag.rb:
class AssetTag < Liquid::Tag
CONFIG = YAML.load_file("_assets.yml")
def initialize(tag_name, name, kind, tokens)
super tag_name, name, tokens
@name = name.to_s.strip
@kind = kind.to_s
end
def render(context)
if Jekyll::ENV == 'production'
markup "/assets/#{name_with_ext}"
else
(assets_for_name.map do |asset|
markup "#{@path}/#{asset}"
end).join("\n")
end
end
def name_with_ext
"#{@name}.#{@ext}"
end
def assets_for_name
if CONFIG[@asset_type].include?(@name)
CONFIG[@asset_type][@name].map do |asset|
asset.gsub(/_site\/(css|js)\//, '')
end
else
name_with_ext
end
end
end
class IncludeJsTag < AssetTag
def initialize(tag_name, name, tokens)
@path = '/js'
@ext = 'js'
@asset_type = 'javascripts'
super tag_name, name, 'js', tokens
end
def markup(src)
%{<script src='#{src}' type='text/javascript'></script>}.to_s
end
end
Liquid::Template.register_tag('include_js', IncludeJsTag)
class IncludeCssTag < AssetTag
def initialize(tag_name, name, tokens)
@path = '/css'
@ext = 'css'
@asset_type = 'stylesheets'
super tag_name, name, 'css', tokens
end
def markup(src)
%{<link href='#{src}' media='screen' rel='stylesheet' type='text/css' />}.to_s
end
end
Liquid::Template.register_tag('include_css', IncludeCssTag)
And then in my layout: I can use {% include_css screen %} and {% include_js application %}.
You may have noticed Jekyll::ENV == 'production' in there. I don't want to use my minified assets while I'm writing posts because if there's a javascript or css problem, it's much harder to debug minified code. So I introduce the concept of a Jekyll environment variable in _plugin/env.rb, where I simply check for the variable in the shell environment:
Jekyll::ENV = (ENV['JEKYLL_ENV'] || 'development')
And then we bring it all together in a rake task, to be run when I'm ready to push my latest changes up to my live blog:
desc "Package app for production"
task :package do
ENV['JEKYLL_ENV'] = 'production'
Rake::Task["build"].invoke
print "Compressing assets..."
system "jammit -o assets -c _assets.yml"
puts "done."
end
Lastly, I have a rake task to deploy:
desc "Deploy latest code in _site to production"
task :deploy do
system(%{
rsync -avz --delete _site/ deploy@mikeferrier.com:/srv/www/mikeferrier.com/
})
end
So my post-writing routine basically looks like this:
- Start up jekyll with
rake preview - Write a post
- When I'm satified, run
rake package deploy
Nice and simple. So far I'm loving the ease and flexibility, and not missing Wordpress one bit. Maybe one day I'll be able to convince Melissa to switch too (not bloody likely.)
Comments
blog comments powered by Disqus