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.