Choosing a templating language in clojure

A brief look at Enlive, Hiccup, and Selmer

One thing that is striking when working in the Clojure ecosystem is the breadth of options available to tackle any given task. The community strives to write simple, interoperable libraries and what results is a daunting number of choices, all with their own benefits and trade-offs. Templating libraries in Clojure are no exception to this rule. The Clojure Toolbox alone lists eleven template languages at last count, and it is not even an exhaustive count of the wide array of techniques available for templating web pages.

In the forthcoming Clojure Cookbook, we cover three such libraries: Enlive, Hiccup, and Selmer. Let’s take a brief look at each of these libraries and discuss their places in the ecosystem.

Hiccup—Pure data, plain and simple

Of the three libraries we’ll cover, Hiccup is the most simple of the bunch. In Hiccup, HTML tags are just vectors of Clojure data; no HTML, no template files, no interpolation (per se), just data. An HTML node is represented as a vector of three parts: tag, attributes (optional), and contents (optional, any number).

Take an anchor tag, for example:

[:a {:href "http://www.rkn.io/application-architecture"} "Application Architecture for Developers"]

Which represents the following HTML:

<a href="http://www.rkn.io/application-architecture">Application Architecture for Developers</a>

To nest multiple elements like you might do in an unordered list, replace the content portion of the vector with the additional child nodes:

[:ul {:class "groceries"}
 [:li "Apples"]
 [:li "Bananas"]
 [:li "Pears"]] ;; oh my!

Which renders as:


  • Apples
  • Bananas
  • Pears

There isn’t really much too the Hiccup format and API. It is so simple, in fact, that it doesn’t have any built-in facilities for variable substitution! That isn’t to say you can’t perform template substitutions with Hiccup, though. Since Hiccup’s nodes are pure data, you can use regular Clojure data manipulation functions to templatize pages and snippets.

For example, to render a grocery list dynamically, you can create a grocery-list function to output an li tag per-item:

(defn grocery-list
  "Transform a list of grocery items into a unordered list"
  [items]
  [:ul {:class "groceries"}
    (for [item items]
    [:li item])])

(grocery-list ["cheese" "eggs"])
;; -> [:ul {:class "groceries"} ([:li "cheese"] [:li "eggs"])]

You can learn more about using Hiccup in Clojure Cookbook’s Templating with Hiccup recipe.

Why Hiccup?

Hiccup is fantastic for templating when you need to go from zero to HTML with minimal fuss. Because HTML nodes are represented as simple Clojure vectors, it is trivial to transform existing data from your application into structured Hiccup data.

While it provides many benefits, Hiccup’s use of pure Clojure data is also one of its hindrances. Depending on the group of developers and designers you work with, it may be difficult for non-technical team members to modify and create Hiccup templates and pages.

Enlive—HTML through and through

Enlive is quite different from Hiccup. Instead of providing a DSL for representing HTML, Enlive ingests plain, old HTML and uses selectors (Ă  la CSS or jQuery) to snip and replace content.

For example, to templatize a blog post page, start with an HTML version of the page with placeholders for values you want to replace:





  Page Title
  
    

Page Title

By Mickey Mouse

Lorem ipsum etc...

After you have some base markup, you can define a template using the deftemplate to ingest post.html and create a function that transforms data like {:title "My Post", :author "Ryan"} into rendered HTML:

(require '[net.cgrand.enlive-html :as html])

(html/deftemplate post-page "post.html"
  [post]
  [:title]         (html/content (:title post))
  [:h1]            (html/content (:title post))
  [:span.author]   (html/content (:author post))
  [:div.post-body] (html/content (:body post)))

Invoking post-page with a map of :title, :author and :body will then produce a rendered version of the page (since the result is a sequence of strings, concatenate them with (reduce str ...)):

(reduce str (post-page {:title "Choosing a Templating Language in Clojure"
                    :author "Ryan Neufeld"
                    :body "..."}))

This produces:

<html>
  <head><title>Choosing a Templating Language in Clojure</title></head>
  <body>
    <h1>Choosing a Templating Language in Clojure</h1>
    <h3>By <span class="author">Ryan Neufeld</span></h3>
    <div class="post-body">...</div>
  </body>
</html>

You can learn more about using Enlive—including repeated elements—in Clojure Cookbook’s Templating with Enlive recipe.

Why Enlive?

Templating with Enlive is definitely a little heavier-handed than Hiccup, no? You’ll need both a file on disk and a solid idea where your pages are going before seeing the benefits of the Enlive approach.

This isn’t necessarily a bad thing, especially if you have non-technical designers or team members. Most importantly, Enlive templates are complete, standalone HTML files; designers can style/edit templates and view the output in a browser without any knowledge of Clojure itself.

Selmer—templates refined

For applications with heavier templating needs, look no further than Selmer. Inspired by Django’s templating language and Jinja, Selmer provides a mustache-like DSL for building templates.

Selmer also provides extensible filters, tags, and inheritance, making it an extremely powerful and flexible templating library.

Like Enlive, most serious Selmer templates live in separate HTML files (though you can render string templates if you’d like). Here’s the “post” example from above replicated in Selmer:





  {{ title }}
  
    

{{ title }}

By {{ author }}

{{ body }}

Unlike the Enlive version, Selmer templates can be trivially rendered via the selmer.parser/render-file function:

(require '[selmer.parser :refer [render-file]])

(render-file "post.html" {:title "Choosing a Templating Language in Clojure" :author "Ryan Neufeld" :body "..."})

This produces:






  Choosing a Templating Language in Clojure
  
    

Choosing a Templating Language in Clojure

By Ryan Neufeld

...

Where Selmer gets really interesting, is in its tags and filters. In the example above, :body is rendered as plain text. In reality, blog posts are commonly written in a markup language like Markdown. Using a custom Selmer filter and the markdown-clj it is possible to render Markdown post bodies directly to HTML.

By hooking up markdown-clj’s md-to-html-string function to Selmer’s rendering engine, it becomes possible to declare substitutions that will render Markdown-formatted text as HTML.

(require '[markdown.core :refer [md-to-html-string]]
         '[selmer.filters])

(selmer.filters/add-filter! :markdown md-to-html-string)

Now, in lieu of {{ body }}, you can write {{ body | markdown | safe }}. (safe is necessary so Selmer doesn’t escape the marked-up body contents.)

You can learn more about using Selmer in Clojure Cookbook’s Templating with Selmer recipe.

Why Selmer?

While it’s possible to use Selmer for smaller pages, where it really shines is in larger web applications. Selmer’s tag, filter, and inheritance features let you focus on building your application, rather than building out fancy templating features.

Overview

Here are my quick and dirty criteria for choosing a templating language in Clojure in a pinch:

  • When trying to introduce a small amount of templating, especially on personal projects, try Hiccup.
  • When working closely with designers and shareholders who don’t speak Clojure, try Enlive.
  • When building large web applications with a large amounts of pages, try Selmer.

This might go without saying, but remember that these are just my suggestions. While I think one templating language may be better in some scenarios and worse in others, that shouldn’t preclude you from choosing any other library. You can use Hiccup to build complicated pages alongside designers and shareholders. You can use Selmer/Enlive to prototype small pages. You’ll just need to consider how that will work for your team.

Like this post? You’ll find more on my website, rkn.io.

tags: