Produce HTML from hiccup in Clojure and ClojureScript.
Squint and cherry support HTML generation as built-in functionality. Some people wanted this functionality in JVM Clojure and ClojureScript as well. That is what this library offers.
Benefits over some (but definitely not all) hiccup libraries may be:
- Generation of HTML is done at compile time, hiccup vectors are never inspected at runtime
- The generated code is small and easy to understand
- The library itself is small (currently around 100 lines of code)
- The library works both in Clojure and ClojureScript
Drawbacks of this library:
- Less dynamic compared to other hiccup libraries (this can also seen as a benefit when it comes to security and performance)
- New and thus not as mature and battle tested as other libraries. Issues + PRs welcome though
- This library only outputs HTML5 when using the
html
macro. If you want to output XML, use thexml
macro.
In this README, all example results are written as strings. In reality they are
a borkdude.html.Html
object which just contains a string. This is done to
prevent issues with double-encoding.
(require '[borkdude.html :refer [html]])
(let [name "Michiel"]
(html [:div {:color :blue :style {:color :blue}}
[:p "Hello there " name
[:ul
[:li 1]
(map (fn [i]
(html [:li i]))
[2 3 4])]]]))
;;=>
"<div color=\"blue\" style=\"color: blue;\"><p>Hello there Michiel<ul><li>1</li><li>2</li><li>3</li><li>4</li></ul></p></div>"
This library doesn't support dynamic creation of attributes in the same way that
some hiccup libraries do. Rather, you have to use the special :&
property to
pass any dynamic properties, reminiscent of the JSX spread operator:
(let [m {:style {:color :blue} :class "foo"}]
(html [:div {:class "bar" :& m}]))
;;=> "<div class=\"foo\" style=\"color: blue;\"></div>"
Any static properties, like :class "bar"
above function as a default which
will be overridden by the dynamic map m
.
A fragment can be written in a similar way as JSX with :<>
as the tag:
(html [:div [:<> "Hello " "world"]]) ;;=> <div>Hello world</div>
Unsafe HTML (which won't be HTML-escaped) can be written with:
(html [:$ "<whatever>]) ;;=> "<whatever>"
Just use function calls for child components:
(defn child-component [{:keys [name]}]
(html [:div "Hello " name]))
(defn App []
(html
[:div
[:div {:color :blue}]
(child-component {:name "Michiel"})]))
(App) ;=> "<div><div color=\"blue\"></div><div>Hello Michiel</div></div>"
To render a sequence of child elements, use html
to render the child element as well:
(html
[:ul
[:li 1]
(map (fn [i] (html [:li i])) [2 3])])
;;=> "<ul><li>1</li><li>2</li><li>3</li></ul>"
Despite the relative simplicity of this library, performance is quite good. Here
is an informal benchmark against hiccup/hiccup
:
(comment
(defn ul []
(html [:ul [:li 1]
(map (fn [i]
(html [:li i]))
[2 3])]))
(time (dotimes [_ 10000000] (ul))) ;; ~3600ms
(defn ul-hiccup []
(hiccup2.core/html [:ul [:li 1]
(map (fn [i]
[:li i])
[2 3])]))
(time (dotimes [_ 10000000] (ul-hiccup))) ;; ~5500ms
)
Note that in hiccup/hiccup
s case, when we wrap the [:li i]
element within a
call to hiccup2.core/html
as well, performance becomes similar as html
since
it can do a similar compile-time optimization.
To install the #html
reader and/or #xml
reader, add the following to data_readers.cljc
:
{html borkdude.html/html-reader
xml borkdude.html/xml-reader}
Then you can write:
#html [:div "Hello"]
#xml [:note {:id "1"}]
Note that these data readers aren't enabled by default since it's not recommended to use unqualified data readers for libraries since this can be a source of conflicts.
When using html
, this library outputs HTML5. So [:br]
compiles to <br>
without a closing tag. Here is an example of how to to output a complete HTML5
document:
(html
[:<>
[:$ "<!DOCTYPE html>"]
[:html {:lang "en"}
[:head
[:meta {:charset "utf-8"}]
[:title "Hi"]]
[:body
[:div "ok"]
[:p
"yes"
[:br]]]]])
MIT, see LICENSE