What's in a Templating Language, Part 1

Photo by Sai Kiran Anagani on Unsplash

Templating languages, which allow developers to take data and insert it into a structured format, are ubiquitous across software. No matter your language or ecosystem, there are most likely numerous templating languages and libraries available covering a myriad of use cases. Most developers will be familiar with tools for HTML generation such as ERB, Haml and Slim for Ruby and Rails, Django Template Language and Jinja for Django and Python, or Mustache and Handlebars for, well, pretty much everything.

Even though there are so many, choosing a language for your current needs is rarely difficult and is often not even an explicit choice, just use whatever your current framework/language/stack provides. However if you are inclined to use a different one, the choices often come down to two questions: Do you want the same thing but different (e.g. Haml and Slim) or do you want more control over what’s exposed to the templates (e.g. Mustache and Handlebars)?

In the Ruby ecosystem, one particular language has become very popular, especially in the realm of static site generators and CMSes: Shopify’s Liquid. Whereas languages like Mustache and Handlebars focus on providing only data and no logic to the template, Liquid bridges the gap between data-only and full-access templating languages. Designed and built for their users to fully customize their storefronts, Shopify needed to make sure that the language was easy for non-programmers to use, provided enough logic constructs to not get in the user’s way, but did not expose Shopify or other users to malicious code.

At Collective Idea, we make heavy use of Liquid in our Harmony CMS to connect a site’s content to its themes. Liquid is also the core renderer for Jekyll, one of the most widely used static site generators today. However, Liquid isn’t without its faults, and anyone who has spent a significant amount of time using Liquid can testify to the many strange quirks and unexpected effects that Liquid supports. That said, I’m not writing this post to complain about Liquid. No software is perfect, and any such software as widely used as Liquid will have edge cases and odd situations that come from iterative improvements and ensuring backwards compatibility. Instead, I’m using Liquid, a language I consider myself an expert in, as a launching point for a personal experiment and a learning opportunity.

As we have been building out a new version of Harmony and building out sites in this app, we’ve been having multiple discussions about the quirks and features in Liquid that seem to regularly get in our way. Questions like: What are the scoping rules that define where {% assign %}‘d variables are accessible? Why is it so hard to create an empty array? and What’s the syntax for this tag or filter again? And it got me thinking, what would it take to build a templating engine similar to Liquid with some of these rules more explicitly spelled out?

Liquid’s greatest strength, and arguably biggest weakness, is that it is in effect an exposed subset of Ruby, the language Liquid is implemented in. Liquid was built up as a Regexp-based text replacement library that over time gained a significant amount of flexibility through its Ruby implementation. The language itself is almost trivial: variables ({{ }}), tags ({% %}) and pipes (|) to chain filters together, with affordances for block-based tags ({% tag %} {% endtag %}). Everything else is definable and redefinable at basically any time before or during a render. As such implementing Liquid in other languages and ecosystems has proven difficult (see issues in liquid-rust or DotLiquid).

Taking a step back, one aspect of the templating languages field that I’ve noticed is that there are very few languages built around sharing across ecosystems. Liquid is Ruby, Django and Jinja are Python. Mustache and Handlebars exist in many languages but all of them are outright re-implementations in that language. If we were to attempt an “improved Liquid”, I would want it to be accessible outside of just Ruby, and more than just a command-line tool. For something to really be usable to other languages, it needs to be embeddable in that language, such as through a Foreign Function Interface (FFI). For a long time, this basically meant C or C++, but thankfully we now have far better options, such as Rust and Go. As I’m more familiar with Go, and as Go has it’s own fully-contained cross-compilation tool-chain (Rust is LLVM and requires a lot of setup for cross compilation) as well as C-style exports and shared library support, I think I will run this experiment in Go.

So here’s the full count of what I plan on building and writing about:

  • A Liquid-influenced templating language
  • Stricter parsing rules for filters and tags
  • Strongly defined scoping rules
  • More syntax support for data types (arrays and objects)
  • Shared library / FFI
  • Command line tool
  • In Go

Sounds like fun! For now I’m calling this project the Language Agnostic Templating Engine, or Late. You can follow my progress on Github at jasonroelofs/late, and here on our blog. My next post will cover topics that make many a programmer (myself included!) often shrug or shudder and walk away: lexing, parsing, and compilers! It’s really not that bad, stay tuned!

Photo of Jason Roelofs

Jason is a senior developer who has worked in the front-end, back-end, and everything in between. He has a deep understanding of all things code and can craft solutions for any problem. Jason leads development of our hosted CMS, Harmony.

Comments