GitHub - borkdude/html: Html generation library inspired by squint's html tag (original) (raw)
html
Produce HTML from hiccup in Clojure and ClojureScript.
Rationale
Squint andcherry 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
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.
Examples
(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;">
Hello there Michiel
- 1
- 2
- 3
- 4
Passing props
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;">"
Any static properties, like :class "bar"
above function as a default which will be overridden by the dynamic map m
.
Fragment
A fragment can be written in a similar way as JSX with :<>
as the tag:
(html [:div [:<> "Hello " "world"]]) ;;=>
Unsafe
Unsafe HTML (which won't be HTML-escaped) can be written with:
(html [:$ "]) ;;=> ""
Child components
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) ;=> "
Child seqs
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])]) ;;=> "
- 1
- 2
- 3
Performance
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.
Data reader
To install the #html
reader, add the following to data_readers.cljc
:
{html borkdude.html/html-reader}
Then you can write:
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.
Complete HTML5 document
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 [:<> [:$ ""] [:html {:lang "en"} [:head [:meta {:charset "utf-8"}] [:title "Hi"]] [:body [:div "ok"] [:p "yes" [:br]]]]])
License
MIT, see LICENSE