August 16th, 2017

Jekyll, Markdown, and Tachyons

I’ve been hearing about Tachyons a lot lately and yesterday decided to try it out by migrating this blog from Bourbon, Neat and custom SASS to Tachyons. I made a lot of great progress but I ran into a problem. How do I style the generated markdown?

*Bourbon and Neat are still great, I just wanted to give Tachyons a try.

If you’re unfamiliar with Tachyons it’s a CSS framework that uses classes to apply styles for almost everything. The problem is that by default you have little control over the HTML generated by markdown posts in Jekyll. Because it’s not easy to add classes the HTML output we can’t style it with Tachyons without some thought.

First Attempt

I had just gotten the layout and blog listing page migrated so I was excited to get the changes pushed to Github and deployed. With It being late, I decided to approach it how any sane developer would, create a custom markdown renderer.

The first step in this journey was getting a custom renderer working. To do this I had to subclass Redcarpet::Render::HTML and create a “converter” class for Jekyll.

To get that going, I created _plugins/tacky_carpet.rb:

require "redcarpet"

class TackyCarpet < Redcarpet::Render::HTML
end

class Jekyll::Converters::Markdown::TackyCarpet
  def initialize
    options = {
      strikethrough: true,
      tables: true,
      fenced_code_blocks: true
    }
    @renderer = Redcarpet::Markdown.new(TackyCarpet, options)
  end

  def convert(content)
    @renderer.render(content)
  end
end

I also had to add markdown: TackyCarpet to my _config.yml file. With this in place I now had a custom markdown renderer that I could build on to start modifying my markdown output.

The next step was to start implementing custom handlers for the elements I wanted to modify. The documentation for Redcarpet::Renderer::HTML is seemingly non-existent so I had to improvise. I used the man page renderer and stripped renderer source to figure out what methods I needed to implement and what arguments they took. Doing so led me to the following:

require "redcarpet"
require "rouge"
require "rouge/plugins/redcarpet"

class TackyCarpet < Redcarpet::Render::HTML
  def header(title, level)
    "<h#{level} class=\"mid-gray mt4 mb3 lh-title\">#{title}</h#{level}>"
  end

  def block_code(code, language)
    lexer = Rouge::Lexer.find_fancy(language, code) || Rouge::Lexers::PlainText
    formatter = Rouge::Formatters::HTML.new(
      css_class: "br2 pa3 highlight #{lexer.tag}"
    )

    formatter.format(lexer.lex(code))
  end

  def codespan(code)
    <<-BLOCK
    <code class="inline br2 pa1">#{code}</code>
    BLOCK
  end

  def link(link, title, content)
    <<-BLOCK
    <a href="#{link}" class="my-purple link underline-hover">
      #{content}
    </a>
    BLOCK
  end
end

# Converter class omitted for brevity

With this custom markdown renderer I was able to deploy and everything looked great. It’s not very maintainable but it works.

A More Sustainable Approach

Writing a custom markdown renderer was a lot of fun but it’s not something I want to maintain long term so I had to find a better approach. Fortunately, it was right under my nose.

I had already setup SASS since I was using Bourbon/Neat before I decided to try Tachyons which means I have access to a “frienemy function”, @extend. If you’ve written a lot of SASS you’d know that @extend is something to avoid. Lucky for us this is exactly the kind of situation @extend is good for.

For this to work I needed to have Tachyon classes available in my SASS files. To do this I installed the tachyons-sass packaged. I used yarn install tachyons-sass but you could clone or vendor the repo if you preferred to avoid the dependency on Node.

I also had to add the following to _config.yml:

sass:
  load_paths:
    - node_modules

Thanks to this wonderful feature I was able to replace my custom renderer with the following:

@import "tachyons-sass/tachyons.scss";

.post {
  h1, h2, h3, h4, h5, h6 {
    @extend .mid-gray, .mt4, .mb3, .lh-title;
  }

  .highlight {
    @extend .br2, .pa3, .highlight;
  }

  a {
    @extend .my-purple, .link, .underline-hover;
  }

  p > code {
    @extend .br2, .pa1;
    background-color: #eee;
    font-size: 12px;
  }

  code {
    font-size: 14px;
  }

  pre {
    @extend .mv0;
  }

  .highlight {
    overflow: scroll;
  }
}

The result is significantly cleaner and has the added benefit of being less code to maintain. It also makes changes easier. Compare tracking down what markdown method you have to implement and overriding it to adding @extend with a few classes.

In Summary

Tachyons is totally rad. It made designing a clean, responsive layout significantly faster and a lot more consistent. Getting it to play nice with markdown was a different challenge but worked out well in the end after some trial and error.