Hiccup in pure Clojure
(require '[huff2.core :as h])When it comes to hiccup libraries, there's a venn-diagram "has ergonomic and modern affordances" and "works on babashka". So huff is my way of saying why not both?
Weavejester's hiccup library runs on babashka, but is missing some of the newer features hiccup afficianados have come to demand.
Lambda Island's hiccup also provides a modern api, but overall I'd still call it a subset of huff's features.
- 🏭 Use functions as components
- 🎨 Style maps work as you'd expect
[:div {:style {:font-size 30}}] - 🔀 Include classes and ids tags in any order
:div.a#id.bor:div.a.c#idor:#id.a.call work!
- 🔒️ HTML-encoded by default
- 👵 Runs on babashka (unlike
lambdaisland/hiccup) - 🏎 Performance: 22-48% faster than hiccup/hiccup for runtime-generated HTML without pre-compilation
- 🙂 Hiccup-style fragments to return multiple forms
(list [:li.a] [:li.b]) - 🙃 Reagent-style fragments to return multiple forms
[:<> [:li.a] [:li.b]] - 🤐 Extreme shorthand syntax
[:. {:color :red}]<div color=red></div> - 🦺 Tested against slightly modified hiccup 2 tests
- 🪗 Extensible grammar
- 📦 raw-strings
Parse tags for id and class (in any order).
(h/html [:div.hello#world"!"])
;; => <div class="hello" id="world">!</div>(println (h/html [:div.left-aligned>p#user-parent>span {:id"user-name"} "Jason"]))
;=> <div class="left-aligned"><p id="user-parent"><span id="user-name">Jason</span></p></div>reagent-like fragment tags
(h/html [:<> [:div"d"] [:<> [:<> [:span"s"]]]])
;; =>
<div>d</div><span>s</span>This is useful for returning multiple elements from a function:
(defntwins [x] [:<>
[:div.a x]
[:div.b x]])
(h/html [:span.parent [twins "elements"]])
;;=>
<span class="parent">
<div class="a">elements</div>
<div class="b">elements</div>
</span>
Nest and combine them with lists to better convey intent to expand:
(h/html
[:ol
[:<> (for [x [12]]
[:li>p.green {:id (str"id-" x)} x])]])
;;=>
<ol>
<li>
<p id=\"id-1\" class=\"green\">1</p>
</li>
<li>
<p id=\"id-2\" class=\"green\">2</p>
</li>
</ol>
(h/html [:div {:style {:border"1px red solid":background-color"#ff00ff"}}])
;; => <div style="background-color:#ff00ff;border:1px red solid;"></div>
(h/html [:. {:style {:width (h/px3)}}])
;;=> <div style=\"width:3px;\"></div>This is nice if you want to e.g. render markdown in the middle of your hiccup. Note: We disable this by default and it must be manually enabled in the call to html or page,
(h/html [:hiccup/raw-html"<div>raw</div>"])
;; =Throws=> :hiccup/raw-html is not allowed. Maybe you meant to set allow-raw to true?
(h/html {:allow-rawtrue} [:hiccup/raw-html"<div>raw</div>"])
;;=> <div>raw</div>Another nice-to-have is to disallow raw html in un-trusted data getting passed into to the compiler, but being able to do that as a dev.
(h/html [:div [:hiccup/raw-html (h/raw-string"<!-- oops.")]])
;; => <div><!-- oops.</div>[More Info]
Write a function that returns hiccup, and call it from the first position of a vector, like in reagent.
(defnstr-info [s]
[:div.info
[:span (apply str (reverse s))]
[:pre.len"Length: " (count s)]])
(h/html [str-info "hello"])
;; =>
<div class="info">
<span>olleh</span>
<pre class="len">Length: 5</pre>
</div>Now you can handcraft your own hiccup grammar. With this power, you can write new tags that can parse (and validate) their inputs.
Let's say you really need a tag to count its children, and put that into the final html.
- Add your new tag to the hiccup schema:
(defmy-schema (h2e/add-schema-branch h/hiccup-schema :my/child-counter-tag))- Write the emitter function for your tag:
(defmethodh/emit:my/child-counter-tag [append! {[_ values] :value} opts]
(append!"I have " (count values) " children."))append! takes strings and will append them internally to a StringBuilder during html generation.
- Call huff2.core/html with your new schema:
;; call:
(h/html (custom-fxns! my-schema)
[:div>h1 [:my/child-counter-tag"one""two""three"]])
;; => <div><h1>I have 3 children.</h1></div>This will be a little faster, and you should prefer it if your schema isnt dynamic.
;; build:
(let [my-fxns (custom-fxns! my-schema)]
(defmy-html (fn my-html [hic] (h/html my-fxns))))
(my-html [:div>h1 [:my/child-counter-tag"one""two""three"]])
;; => <div><h1>I have 3 children.</h1></div>More details in the huff extension tests.