A new Hiccup compiler for Clojurescript

Summary

In this post I’ll introduce a new hiccup compiler for Clojurescript that:

Never interprets at runtime, thus has zero runtime overhead guaranteed similarly to JSX. Allows specifying custom tags, predefining tags for fragments and interop with JS React components. Optimization for production output

Introduction

Javascript has JSX, which looks like HTML:

<div id=”foo”>

<img src={src} />

</div>

This would get transpiled to roughly the following Javascript code:

React.createElement("div", {id: "foo"},

React.createElement("img", {src: src}));

A note on terminology: The result of the above expression (i.e. what React.creatElement returns) will be called a “ReactNode”.

Clojurescript doesn’t allow this kind of syntax, but the community came up with Hiccup to define HTML a long time ago (before React was a thing).

It’s syntax is more succinct and very natural to a Clojure developer:

[:div {:id "foo"}

[:img {:src src}]]

You have all the goodies of staying in Clojure syntax such as editing your code with Paredit.

One of the great things about Clojurescript is that we have macros. This means, we can precompile the hiccup syntax to fast React.createElement calls, just like a JSX compiler does. No compiler plugin necessary.

This is exactly what Sablono does. In addition Sablono also ships with an interpreter in case the user forgot to apply the macro to some hiccup.

We can see it in action by calling macroexpand on the html macro of sablono:

(macroexpand-1 '(html [:a {} (foo)])) => (js/React.createElement "a" nil (sablono.interpreter/interpret (foo)))

Here (foo) is a function call which could return more hiccup, such as [:span "x"] or it might just return a string or a even a ReactNode. Note, it’s also possible to avoid the interpretation call.

Why not sablono?

Doing anything over and over again at runtime while you could’ve done it at compile time only once is wasteful. This is the main reason why I decided to make a (friendly) fork of it. Other reasons:

No support for React fragments

No support for creating ReactNodes from React components: ( <MyComponent speedy=1><span>hi</span></MyComponent> )

) No customization support such as adding a little DSL when you’re working on big projects.

Introducing Hicada

Hicada (Hiccup Compiler aus dem Allgäu) is a new hiccup compiler that supports the above and eliminates the runtime interpretation. I.e. if you fail to precompile your hiccup, for instance inside a function:

(html

[:div "List:"

(mapv (fn [x] [:span x]) xs)])

you’ll see a runtime error from React complaining that it doesn’t understand your CLJS data structure ( [:span x] ). The fix is easy: Add a (html ...) call inside your inner fn .

If you first checkout the main namespace hicada.compiler you’ll probably notice one weird thing: There is no macro for you to use anywhere!

This is intentional: You’re required to create a simple macro and call the function in hicada.compiler with your desired config:

Simple macro

Why this API? IMO, as soon as you use a library at a lot of places in your code (which you would in a typical SPA) you’re constricting yourself unnecessarily:

You can’t easily refactor

If the library offers a new functionality, it’s hard to “opt-in” or “opt-out” of it globally.

You cannot easily opt-in for optimizations in production builds (more on that later)

By wrapping it, you’re much more flexible as your project progresses (I’ll show some real world examples later). You call the library (hicada) only once and your app code calls your own macro throughout your project.

Have you ever noticed how you’ve been able to use React in CLJS (be it Om, Reagent or Rum) and have not had to run any codemod scripts? It’s because the libraries take care of any backwards incompatibilities and provide you with a consistent API.

New functionalities and differences

Let’s now talk about the new features of hicada: