Building My Site: Server Side Code
24 June 2015
In my first post, I explained how I’m using Jekyll, Git, and bash aliases to publish this site. I chose this setup because it keeps the server-side of things simple: there’s no database, and no code executing when a page is served. Just some markdown files which get mushed together with templates to produce static HTML files.
For the most part, the site follows that simple formula. But there are a few instances where I need a bit more control over formatting, or a bit of logic to avoid maintaining links by hand. Enter the YAML serialisation format and the Liquid template language.
Most of this site is pretty vanilla, and since the Jekyll documentation is quite good I won’t rehash the basics here. Similarly, the Wikipedia YAML article and Liquid documentation provide good guides to YAML and Liquid, respectively (the Shopify Liquid documentation is also good). Instead, I’ll focus on how I’ve applied these technologies to the specific needs of my site.
CV
Markdown sacrificies sophisticated formatting to be easy to read and write. That’s great for basic article pages like blog posts. But a cv needs to communicate a lot of information quickly, and so requires more sophisticated structure, and formatting to communicate that structure.
The most straightforward way to add that structure is to put the markup directly in the markdown, but that means instead of writing markdown like this:
* __Web Standards Project, Jul. 1998–Mar. 2007:__ Served as project manager and volunteer coordinator during launch. Founded task forces and served on the steering committee. Wrote and edited opinion pieces and press releases. Promoted initiatives in online communities and email lists.
You write HTML like this:
<li>
<h2><strong>Web Standards Project<span> • </span></strong>Jul. 1998–Mar. 2007</h2>
<p>Served as project manager and volunteer coordinator during launch. Founded task forces and served on the steering committee. Wrote and edited opinion pieces and press releases. Promoted initiatives in online communities and email lists.</p>
</li>
It’s less legible, and also more brittle: miss out a </ul>
and you have a broken page. For a write-and-forget page that’s not a big concern. But if I’m looking for a job I need to be able to tweak the text to match the role I’m applying for. Each edit is an opportunity to break the HTML, one I seem constitutionally incapable of declining.
I also need a PDF version to print or send via email. It’s possible to generate a PDF from the HTML version, and printing to PDF from Safari will even get you working hyperlinks. But getting a precise print layout with HTML + CSS is still more time-consuming than with a word processor. Until I polish up the print styles for my CV, I’m stuck with two versions and copying changes between them. Cutting and pasting around HTML tags just is just begging for errors, but wrapping structured text with repetitive HTML is exactly the problem template languages like Liquid were designed to solve.
A CV is mostly a series of lists: previous roles, skills, and so on. This is the perfect sort of data for YAML, which lets you create lists and key-value pairs with a bit of plain text formatting:
- company: Web Standards Project
dates: Jul. 1998–Mar. 2007
description: Served as project manager and volunteer coordinator during launch. Founded task forces and served on the steering committee. Wrote and edited opinion pieces and press releases. Promoted initiatives in online communities and email lists.
I then use Liquid for
loops to iterate through the various lists and apply the appropriate markup, using if
blocks to hide sections that aren’t present (descriptions for very old jobs, for example):
<ol>
{% for job in page.experience %}
<li><h2><strong>{{ job.company }}<span> • </span></strong>{{ job.dates }}</h2>
{% if job.description %}<p>{{ job.description }}</p>{% endif %}
</li>
{% endfor %}
</ol>
This system minimises cut-and-paste errors and makes it quicker to adjust the markup as there’s only one list item of any given type to amend. It works well enough that even once I refine my print styles to the point I can generate the PDF by printing the web page, I’ll still want to use YAML + Liquid (or something similar) to generate that page.
Portfolio
Unlike the CV, it’s unlikely I’ll ever work out a system for generating web and PDF versions of my portfolio from the same source. For one, the PDF is a single multi-page file where on the web each project really needs its own page to keep downloads manageable and facilitate direct linking.
As well, many of the images were originally vector diagrams. The can be embedded in a PDF as native vectors without making the file size ridiculous. But they’re too complex to convert cleanly to SVG files, and even after GZIP compression the files are often larger than web-resolution PNG counterparts.
Finally, a layout optimised for print is typically very different than one optimised for responsive display on the web, and each page of the PDF is formatted uniquely for its content. That’s no big deal when using grids in a layout program. But with HTML + CSS, changing the content also means changing the CSS, and maybe the markup, and then re-testing across multiple browsers. Separate versions of the portfolio for print and web are probably here to stay.
Fortunately, the items in my portfolio are basic article-type pages, so markdown works fine and makes it easy to cut-and-paste between the web and print versions. So I could have created these as ‘static’ Jekyll pages, but I would have had to create the portfolio index and section navigation by hand. Instead, I’ve used a collection.
Recent versions of Jekyll have built-in support for collections, which are sets of related pages with their own namespace and properties. Unfortunately, my web host only supports an older version of Jekyll. So instead I used the jekyll-page-collections plugin.
It’s easy enough to use: just drop the plugin file into Jekyll’s _plugins directory, add your collection name to a collections: list in the _config.yaml file, and create the pages in the collection in a directory with the same name as your collection but preceded by an _. You have to name collection pages with the w/yyyy-mm-dd-[slug] convention used by Jekyll posts, but that makes it easy to reorder them by changing the dates or to hide a page by removing the date entirely (e.g. to change the list of projects for specific job opportunity). Jekyll permalinks keep the URLs tidy and consistent, regardless of the dates in the filenames.
For the subnav and portfolio index, I add a description, navigation name, and thumbnail image to the YAML front matter for each project. I then loop through the collection to generate the appropriate list. Everything about a given project (save images) is in one file, which makes for easy updates.
Images
Images are really the only part of the site that required a bit of cleverness on the server side, and even then the solution is pretty simple.
Markdown allows you to embed images, but it’s really basic: it only allows for title
and alt
attributes; no class
or id
. So there’s no way to float the image. And if you need a caption or link, you’re out of luck.
So instead of placing the images inline, I put the images for a project or blog post in the YAML front matter, like so:
images:
- small: it-portal-concept-model-small.png
large: it-portal-concept-model-large.png
caption: Concept model
alt: Content model
Then in the post, I use an HTML comment to place the images:
<!-- image1 -->
Using an HTML comment has two advantages. First, markdown processors ignore HTML, so they won’t munge it. Second, if something goes wrong and it doesn’t get replaced with an image, there’s just a tiny, nonsensical comment in the page source rather than visible breakage. One could argue this is abusing HTML comments, which should only be actual comments. But logically, if everything is working it never gets into an HTML document, so arguing HTML purity seems a bit daft. And from a practical standpoint, Apache SSIs have been ‘abusing’ comments for a couple decades now with no harm done. If you’re still not persuaded, this is where the people who care live. Tell them your trouble.
In my templates, I assign the rendered HTML page content
to a withImages
variable, then us an if
statement to check if the page has an images
array. If so, I loop through the array one at a time. For each iteration, I generate a replacement slug:
{% capture slug %}<!-- image{{ forloop.index }} -->{% endcapture %}
I use another Liquid capture
block to capture the HTML for the image, filed with the values from the current image from the images
array. As with the CV, I use for
statements to include only the elements (classes, link to an enlargement, caption, etc.) present for that image. I then use the Liquid replace
filter to replace the HTML comment in the text with the image markup:
{% assign withImages = withImages | replace:slug,markup %}
All that goes in a Liquid include
for easy maintenance.
Back in the template, I output the contents of withImages
into the page. I could just do the replacements right in the content
variable, but it seems prudent to keep the unadulterated page content around.
The system is a bit brittle, as it requires knowing exactly what order images are in the YAML array. But as the array is defined in the same file as the slugs are placed that doesn’t concern me overmuch.
As well, this sort of output filter could arguably be better implemented as a Jekyll plugin written in Ruby, if only for performance reasons. But as it only runs for pages with an images
array, and I’d want to be able to edit the HTML templates without digging into Ruby, the benefits to doing so seem small.
Bottom line, I’ve got a way to place images wherever I wish within an otherwise-vanilla markdown page and include classes (e.g. for floating them with CSS), a link to an enlargement, alt
text, and so on. I can extend it whenever I like by editing a single include file and, provided the enhancements are wrapped if
statements, old pages will continue to work undisturbed. Eventually, managing all the if
statements may get cumbersome, but for now it does the job.
Series navigation
The last little bit of Liquid-fu is the navigation for posts belonging to a series, like this one. That’s accomplished by looking for a series
property in the YAML front matter of each post as its rendered. I could have used page.tags
or page.categories
, but a series doesn’t seem to me to be the same thing as either. Rather than piggyback on a not-quite-right property that might cause headaches should I decide to categorise or tag my posts later I elected to create my own property specifically for series of posts.
If the series
property is there and has a value, I loop through the build-in Jekyll site.posts
collection, testing each for a matching series
. When I find one, I add a link to that post to the text in a variable. When I’m done collecting links, I place the contents in the page. It looks like this:
{% for post in site.posts %}
{% if post.series == page.series %}
{% capture series_nav %}
<li><a{% if post.title == page.title %} class="active"{% endif %} href='{{ post.url }}'>{{ post.title }}</a></li>{{ series_nav }}
{% endcapture %}
{% endif %}
{% endfor %}
The existing series_nav
is inserted at the end the capture
block because the site.posts
collection is in reverse-chronological order. I want the posts in the series navigation to be in chronological order, so I have to put each new link before the ones I’ve already created.
Also, to get the active page indicator I test to see if the post.title
of the matched post is the same as the current page.title
and add a class if it is. Using a content property like this is a bit dirty; it’s generally better to abstract this sort of thing into an ID. But since both sides of the test are reading a value derived from exactly the same bit of text in the same file, the dangers seem minimal and it avoids having to add an additional ID property to the frontmatter.
When this blog has more posts, looping through every post on the site may get prohibitively slow and I’ll need to look into something more clever. But as with the images system, for now it does what I need.
Escaping Liquid tags
The last bit worth mentioning is something I ran into while writing this post: escaping Liquid tags for inclusion in code samples.
My Jekyll installation uses an older version the Pygments syntax highlighter that doesn’t support Liquid (the most recent versions does, but I can’t use it because shared hosting). Instead, I’ve resorted to using Pygments’ HTML lexer and escaping the Liquid opening tags as a Liquid literal ({{" "}}
), like this:
{{"{%"}} highlight html %}
<h1>{{"{%"}} post.title %}</h1>
{{"{%"}} endhighlight %}
I got the idea from a post by Scott Tesoriere. It looks gnarly, but it works (as a sidenote, the double-escapes above were created using {{' '}}
to escape the escape).
That’s pretty much all there is to the server-side code for this blog. So far, it working exactly as I hoped. Editing and adding pages is much quicker and less error-prone with YAML and markdown than were I editing HTML directly, and editing the Liquid templates and includes is considerably easier than mucking about with gobs of repetitive HTML. For once, it seems the ‘simple solution’ actually is.