Mike Ferrier

I beat code into submission.

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:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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:

1
2
3
4
5
6
7
8
9
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:

1
2
3
4
5
6
7
8
9
10
11
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
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:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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:

1
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:

1
2
3
4
5
6
7
8
9
10
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:

1
2
3
4
5
6
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:

  1. Start up jekyll with rake preview
  2. Write a post
  3. 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