Skip to content

Instantly share code, notes, and snippets.

@mmcgrana
Created January 9, 2009 15:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mmcgrana/45136 to your computer and use it in GitHub Desktop.
Save mmcgrana/45136 to your computer and use it in GitHub Desktop.
clj-html.core & compojure.html comparison conclusions
be sure to see syntax.clj and performance.clj first
* Interoperability between clj-html.core and compojure.html might be feasble, as
their syntax are similar.
* Helper functions must be written to return html Strings, not vector syntax, if
they are to be used with both libraries.
* clj-html.core is between 1 and 2 orders of magnitude faster than compjure.html.
* Users need to decide how they want to balance the speed of clj-html.core with
the syntatic flexibility of compojure.html
* At least one knows that if an app starts with compojure.html, one could later
easily switch to clj-html for performace critial templates.
;;; clj-html.core & compojure.html performance
(use '[clj-html.core :rename {html htmlc htmli htmli2}]
'clj-html.utils
'[compojure.html :rename {html htmli}])
(defn- htmli-item-partial
[item]
[:div.item
[:p.special item]])
(defn- htmlc-item-partial
[item]
(htmlc
[:div.item
[:p.special item]]))
(defn- string-builder-partial
[item]
(let [builder (StringBuilder.)]
(.append builder "<div id=\"item\"><p class=\"special\"")
(.append builder item)
(.append builder "</div>")
(.toString builder)))
(defn- htmli-template
[html-f items title header]
(html-f
[:html
[:head [:title title]]
[:body
[:div#container
[:div#content {:id (str "foo" "bar") :class (str "biz" "bat")}
(if true
[:h1#custom-header (str "Custom " header)]
[:h1#header header])
[:div#items
(map htmli-item-partial items)]]]]]))
(defn- htmlc-template
[items title header]
(htmlc
[:html
[:head [:title title]]
[:body
[:div#container
[:div#content {:id (str "foo" "bar") :class (str "biz" "bat")}
(if true
(htmlc [:h1#custom-header (str "Custom " header)])
(htmlc [:h1#header header]))
[:div#items
(map-str htmlc-item-partial items)]]]]]))
(defn- string-builder-template
[items title header]
(let [builder (StringBuilder.)]
(.append builder "<html><head><title>")
(.append builder (str title))
(.append builder "</title></head><body><div id=\"container\"><div id=\"content\" id=\"")
(.append builder (str "foo" "bar"))
(.append builder "\" class=\"")
(.append builder (str "biz" "bat"))
(.append builder "\">")
(if true
(do
(.append builder "<h1 id=\"custom-header\">")
(.append builder (str "Custom" header))
(.append builder "</h1>"))
(do
(.append builder "<h1 id=\"header\">")
(.append builder (str header))
(.append builder "</h1>")))
(doseq [item items]
(.append builder (string-builder-partial item)))
(.append builder "</div></div></body></html>")
(.toString builder)))
(def items ["foo" "bar" "bat"])
(def title "title")
(def header "header")
(defn bench
[name f]
(print (str name ": "))
(time (dotimes [_ 10000] (f items title header))))
(assert (= (htmlc-template items title header)
((partial htmli-template htmli2) items title header)))
(dotimes [_ 2]
(bench "compojure.html " (partial htmli-template htmli))
(bench "clj-html.core(i) " (partial htmli-template htmli2))
(bench "clj-html.core(c) " htmlc-template)
(bench "StringBuilder " string-builder-template)
(println))
; $ clj performace.clj
; compojure.html : "Elapsed time: 11111.611 msecs"
; clj-html.core(i) : "Elapsed time: 1892.746 msecs"
; clj-html.core(c) : "Elapsed time: 136.493 msecs"
; StringBuilder : "Elapsed time: 66.032 msecs"
;
; compojure.html : "Elapsed time: 11526.314 msecs"
; clj-html.core(i) : "Elapsed time: 1849.2 msecs"
; clj-html.core(c) : "Elapsed time: 126.347 msecs"
; StringBuilder : "Elapsed time: 62.133 msecs"
;;; clj-html.core & compojure.html syntax
; In the examples that follow I will use htmli (denoting 'interpreted') for
; compojure.html/html and htmlc ('compiled') for clj-html.core/html:
(use '[clj-html.core :rename {html htmlc}]
'[compojure.html :rename {html htmli}])
; Generally the two are similar:
(htmli [:body [:div#foo "content"]])
"<body>\n <div id=\"foo\">\n content\n </div>\n</body>\n"
(htmlc [:body [:div#foo "content"]])
"<body><div id=\"foo\">content</div></body>"
; One minor difference: clj-html/html can have multiple literal class names:
(htmli [:p.foo.bar "content"])
java.lang.NullPointerException
(htmlc [:p.foo.bar "content"])
"<p class=\"foo bar\">content</p>"
; For clj-html, all tag keywords must be literal, i.e. locals or vars can not
; be used for dynamic tag names:
(let [t :p] (htmli [t "content"]))
"<p>\n content\n</p>\n"
(let [t :p] (htmlc [t "content"]))
java.lang.Exception: Unrecognized form [t "content"]
; Similarly, for clj-html the attribute map must be a literal map. Keys and
; values can be dynamic, but the outermost form must be a literal {}:
(let [k :id v "an_id"] (htmli [:p {k v} "content"]))
"<p id=\"an_id\">\n content\n</p>\n"
(htmli [:p (merge {:id "an_id"} {:class "a_class"}) "content"])
"<p class=\"a_class\" id=\"an_id\">\n content\n</p>\n"
(let [k :id v "an_id"] (htmlc [:p {k v} "content"]))
"<p id=\"an_id\">content</p>"
(htmlc [:p (merge {:id "an_id"} {:class "a_class"}) "content"])
"<p>{:class \"a_class\", :id \"an_id\"}content</p>"
; One big set of differences is how the two libraries support
; mixing of the literal vector markp syntax with calls to Clojure helper
; functions; clj-html generally requires more (html ...) calls inside calls to
; helper functions. This is the case becuase in compojure calls to helper
; functions can return literal markup for compojure but in clj-html must return
; html snippets:
(defn compojure-helper [x] [:div.styled x])
(htmli [:div.outer (compojure-helper [:p "inner"])])
"<div class=\"outer\">\n <div class=\"styled\">\n <p>\n inner\n </p>\n </div>\n</div>\n"
(defn clj-html-helper [x] (htmlc [:div.styled x]))
(htmlc [:div.outer (clj-html-helper (htmlc [:p "inner"]))])
"<div class=\"outer\"><div class=\"styled\"><p>inner</p></div></div>"
; Another set of differences is how the two handle rendering content for a
; collection.
; One case to consider is a helper function that needs to be applied to each
; element in a collection:
(def items '("foo" "bar"))
(defn compojure-partial [x] [:div.item [:p x]])
(htmli (map compojure-partial items))
"<div class=\"item\">\n <p>\n foo\n </p>\n</div>\n<div class=\"item\">\n <p>\n bar\n </p>\n</div>\n"
(defn clj-html-partial [x] (htmlc [:div.item [:p x]]))
(htmlc (map-str clj-html-partial items))
"<div class=\"item\"><p>foo</p></div><div class=\"item\"><p>bar</p></div>"
; Another collection-related case is when we want to write inline the markup
; to use for each item:
(htmli (for [item items] [:div.item [:p item]]))
"<div class=\"item\">\n <p>\n foo\n </p>\n</div>\n<div class=\"item\">\n <p>\n bar\n </p>\n</div>\n"
(htmlc (domap-str [item items] (htmlc [:div.item [:p item]])))
"<div class=\"item\"><p>foo</p></div><div class=\"item\"><p>bar</p></div>"
; Though with a macro the case for clj-html can be improved:
(defmacro htmlfor
[[binding-sym coll-form] & body]
`(domap-str [~binding-sym ~coll-form] (htmlc ~@body)))
(htmlc (htmlfor [item items] [:div.item [:p item]]))
; Note that the two libraries currently use slightly differnt utility functions
; for helping with iteration:
* clj-html.core/map-str and compojure.html/str-map are identical except for name
* clj-html.core/domap-str serves a similar goal in clj-html templates as
compojure.html/domap serves in compojure templates
; Finally, for templates with conditionals we need more (html calls when using
; clj-html:
(htmli
[:div
(if-let [maybe-content "content"]
[:p maybe-content])])
"<div>\n <p>\n content\n </p>\n</div>\n"
(htmlc
[:div
(if-let [maybe-content "content"]
(htmlc [:p maybe-content]))])
"<div><p>content</p></div>"
; Though again we could invest in similar macros like htmlif and htmlif-let
; for any common cases.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment